File Coverage

lib/Class/Plain.xs
Criterion Covered Total %
statement 156 184 84.7
branch 64 108 59.2
condition n/a
subroutine n/a
pod n/a
total 220 292 75.3


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2              
3             #include "EXTERN.h"
4             #include "perl.h"
5             #include "XSUB.h"
6              
7             #include "XSParseKeyword.h"
8              
9             #include "XSParseSublike.h"
10              
11             #include "perl-backcompat.c.inc"
12             #include "sv_setrv.c.inc"
13              
14             #include "perl-additions.c.inc"
15             #include "lexer-additions.c.inc"
16             #include "forbid_outofblock_ops.c.inc"
17             #include "force_list_keeping_pushmark.c.inc"
18             #include "optree-additions.c.inc"
19             #include "newOP_CUSTOM.c.inc"
20              
21             #include "class_plain_parser.h"
22             #include "class_plain_class.h"
23             #include "class_plain_field.h"
24             #include "class_plain_method.h"
25              
26             /**********************************
27             * Class and Field Implementation *
28             **********************************/
29              
30             static XOP xop_methstart;
31 47           static OP *pp_methstart(pTHX)
32             {
33 47           SV *self = av_shift(GvAV(PL_defgv));
34              
35 47 100         if(!SvROK(self) || !SvOBJECT(SvRV(self)))
    50          
36 1           croak("Cannot invoke method on a non-instance");
37              
38 46           save_clearsv(&PAD_SVl(1));
39 46           sv_setsv(PAD_SVl(1), self);
40              
41 46           return PL_op->op_next;
42             }
43              
44 43           OP *ClassPlain_newMETHSTARTOP(pTHX_ U32 flags)
45             {
46             OP *op = newOP_CUSTOM(&pp_methstart, flags);
47 43           op->op_private = (U8)(flags >> 8);
48 43           return op;
49             }
50              
51             static XOP xop_commonmethstart;
52 21           static OP *pp_commonmethstart(pTHX)
53             {
54 21           SV *self = av_shift(GvAV(PL_defgv));
55              
56 21 50         if(SvROK(self))
57             /* TODO: Should handle this somehow */
58 0           croak("Cannot invoke common method on an instance");
59              
60 21           save_clearsv(&PAD_SVl(1));
61 21           sv_setsv(PAD_SVl(1), self);
62              
63 21           return PL_op->op_next;
64             }
65              
66 18           OP *ClassPlain_newCOMMONMETHSTARTOP(pTHX_ U32 flags)
67             {
68             OP *op = newOP_CUSTOM(&pp_commonmethstart, flags);
69 18           op->op_private = (U8)(flags >> 8);
70 18           return op;
71             }
72              
73             /* The metadata on the currently-compiling class */
74             #define compclass_meta S_compclass_meta(aTHX)
75 199           static ClassMeta *S_compclass_meta(pTHX)
76             {
77 199           SV **svp = hv_fetchs(GvHV(PL_hintgv), "Class::Plain/compclass_meta", 0);
78 199 50         if(!svp || !*svp || !SvOK(*svp))
    50          
    50          
    0          
    0          
79             return NULL;
80 199 50         return (ClassMeta *)(intptr_t)SvIV(*svp);
81             }
82              
83             #define have_compclass_meta S_have_compclass_meta(aTHX)
84 106           static bool S_have_compclass_meta(pTHX)
85             {
86 106           SV **svp = hv_fetchs(GvHV(PL_hintgv), "Class::Plain/compclass_meta", 0);
87 106 100         if(!svp || !*svp)
    50          
88             return false;
89              
90 103 50         if(SvOK(*svp) && SvIV(*svp))
    0          
    0          
    50          
    50          
91             return true;
92              
93             return false;
94             }
95              
96             #define compclass_meta_set(meta) S_compclass_meta_set(aTHX_ meta)
97 39           static void S_compclass_meta_set(pTHX_ ClassMeta *meta)
98             {
99 39           SV *sv = *hv_fetchs(GvHV(PL_hintgv), "Class::Plain/compclass_meta", GV_ADD);
100 39           sv_setiv(sv, (IV)(intptr_t)meta);
101 39           }
102              
103             #define is_valid_ident_utf8(s) S_is_valid_ident_utf8(aTHX_ s)
104             static bool S_is_valid_ident_utf8(pTHX_ const U8 *s)
105             {
106             const U8 *e = s + strlen((char *)s);
107              
108             if(!isIDFIRST_utf8_safe(s, e))
109             return false;
110              
111             s += UTF8SKIP(s);
112             while(*s) {
113             if(!isIDCONT_utf8_safe(s, e))
114             return false;
115             s += UTF8SKIP(s);
116             }
117              
118             return true;
119             }
120              
121 37           static void inplace_trim_whitespace(SV *sv)
122             {
123 37 100         if(!SvPOK(sv) || !SvCUR(sv))
    100          
124             return;
125              
126 12           char *dst = SvPVX(sv);
127             char *src = dst;
128              
129 12 50         while(*src && isSPACE(*src))
    50          
130 0           src++;
131              
132 12 50         if(src > dst) {
133 0           size_t offset = src - dst;
134 0           Move(src, dst, SvCUR(sv) - offset, char);
135 0           SvCUR(sv) -= offset;
136             }
137              
138 12           src = dst + SvCUR(sv) - 1;
139 12 50         while(src > dst && isSPACE(*src))
    50          
140 0           src--;
141              
142 12           SvCUR(sv) = src - dst + 1;
143 12           dst[SvCUR(sv)] = 0;
144             }
145              
146 18           static void S_apply_method_common(pTHX_ MethodMeta *meta, const char *val, void *_data)
147             {
148 18           meta->is_common = true;
149 18           }
150              
151             static struct MethodAttributeDefinition method_attributes[] = {
152             { "common", &S_apply_method_common, NULL },
153             { 0 }
154             };
155              
156             /*******************
157             * Custom Keywords *
158             *******************/
159              
160 39           static int build_classlike(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
161             {
162             int argi = 0;
163            
164              
165 39           SV *packagename = args[argi++]->sv;
166             /* Grrr; XPK bug */
167 39 50         if(!packagename)
168 0           croak("Expected a class name after 'class'");
169              
170 39           IV type = (IV)(intptr_t)hookdata;
171              
172 39           ClassMeta *meta = ClassPlain_create_class(aTHX_ type, packagename);
173              
174 39           int nattrs = args[argi++]->i;
175 39 100         if(nattrs) {
176             int i;
177 21 100         for(i = 0; i < nattrs; i++) {
178 11           SV *attrname = args[argi]->attr.name;
179 11           SV *attrval = args[argi]->attr.value;
180              
181 11           inplace_trim_whitespace(attrval);
182              
183 11           ClassPlain_class_apply_attribute(aTHX_ meta, SvPVX(attrname), attrval);
184              
185 11           argi++;
186             }
187             }
188              
189 39           ClassPlain_begin_class_block(aTHX_ meta);
190              
191             /* At this point XS::Parse::Keyword has parsed all it can. From here we will
192             * take over to perform the odd "block or statement" behaviour of `class`
193             * keywords
194             */
195              
196             bool is_block;
197              
198 39 50         if(lex_consume_unichar('{')) {
199             is_block = true;
200 39           ENTER;
201             }
202 0 0         else if(lex_consume_unichar(';')) {
203             is_block = false;
204             }
205             else
206 0           croak("Expected a block or ';'");
207              
208             /* CARGOCULT from perl/op.c:Perl_package() */
209             {
210 39           SAVEGENERICSV(PL_curstash);
211 39           save_item(PL_curstname);
212              
213 39           PL_curstash = (HV *)SvREFCNT_inc(gv_stashsv(meta->name, GV_ADD));
214 39           sv_setsv(PL_curstname, packagename);
215              
216 39           PL_hints |= HINT_BLOCK_SCOPE;
217 39           PL_parser->copline = NOLINE;
218             }
219              
220 39 50         if(is_block) {
221 39           I32 save_ix = block_start(TRUE);
222 39           compclass_meta_set(meta);
223              
224 39           OP *body = parse_stmtseq(0);
225 39           body = block_end(save_ix, body);
226              
227 39 50         if(!lex_consume_unichar('}'))
228 0           croak("Expected }");
229              
230 39           LEAVE;
231              
232             /* CARGOCULT from perl/perly.y:PACKAGE BAREWORD BAREWORD '{' */
233             /* a block is a loop that happens once */
234 39           *out = op_append_elem(OP_LINESEQ,
235             newWHILEOP(0, 1, NULL, NULL, body, NULL, 0),
236             newSVOP(OP_CONST, 0, &PL_sv_yes));
237 39           return KEYWORD_PLUGIN_STMT;
238             }
239             else {
240 0           SAVEHINTS();
241 0           compclass_meta_set(meta);
242              
243 0           *out = newSVOP(OP_CONST, 0, &PL_sv_yes);
244 0           return KEYWORD_PLUGIN_STMT;
245             }
246             }
247              
248             static const struct XSParseKeywordPieceType pieces_classlike[] = {
249             XPK_PACKAGENAME,
250             /* This should really a repeated (tagged?) choice of a number of things, but
251             * right now there's only one thing permitted here anyway
252             */
253             XPK_ATTRIBUTES,
254             {0}
255             };
256              
257             static const struct XSParseKeywordHooks kwhooks_class = {
258             .permit_hintkey = "Class::Plain/class",
259             .pieces = pieces_classlike,
260             .build = &build_classlike,
261             };
262              
263 40           static void check_field(pTHX_ void *hookdata)
264             {
265             char *kwname = hookdata;
266            
267 40 100         if(!have_compclass_meta)
268 2           croak("Cannot '%s' outside of 'class'", kwname);
269              
270 38 50         if(!sv_eq(PL_curstname, compclass_meta->name))
271 0           croak("Current package name no longer matches current class (%" SVf " vs %" SVf ")",
272 0           PL_curstname, compclass_meta->name);
273 38           }
274              
275 38           static int build_field(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
276             {
277             int argi = 0;
278              
279 38           SV *name = args[argi++]->sv;
280              
281 38           FieldMeta *fieldmeta = ClassPlain_class_add_field(aTHX_ compclass_meta, name);
282             SvREFCNT_dec(name);
283              
284 38           int nattrs = args[argi++]->i;
285 38 100         if(nattrs) {
286 50 100         while(argi < (nattrs+2)) {
287 26           SV *attrname = args[argi]->attr.name;
288 26           SV *attrval = args[argi]->attr.value;
289              
290 26           inplace_trim_whitespace(attrval);
291              
292 26           ClassPlain_field_apply_attribute(aTHX_ fieldmeta, SvPVX(attrname), attrval);
293              
294 26 50         if(attrval)
295             SvREFCNT_dec(attrval);
296              
297 26           argi++;
298             }
299             }
300              
301 38           return KEYWORD_PLUGIN_STMT;
302             }
303              
304             static const struct XSParseKeywordHooks kwhooks_field = {
305             .flags = XPK_FLAG_STMT,
306              
307             .check = &check_field,
308              
309             .permit_hintkey = "Class::Plain/field",
310             .pieces = (const struct XSParseKeywordPieceType []){
311             XPK_IDENT,
312             XPK_ATTRIBUTES,
313             {0}
314             },
315             .build = &build_field,
316             };
317 66           static bool parse_method_permit(pTHX_ void *hookdata)
318             {
319 66 100         if(!have_compclass_meta)
320 1           croak("Cannot 'method' outside of 'class'");
321              
322 65 50         if(!sv_eq(PL_curstname, compclass_meta->name))
323 0           croak("Current package name no longer matches current class (%" SVf " vs %" SVf ")",
324 0           PL_curstname, compclass_meta->name);
325              
326 65           return true;
327             }
328              
329 65           static void parse_method_pre_subparse(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
330             {
331             /* While creating the new scope CV we need to ENTER a block so as not to
332             * break any interpvars
333             */
334 65           ENTER;
335 65           SAVESPTR(PL_comppad);
336 65           SAVESPTR(PL_comppad_name);
337 65           SAVESPTR(PL_curpad);
338              
339 65           intro_my();
340              
341             MethodMeta *compmethodmeta;
342 65           Newx(compmethodmeta, 1, MethodMeta);
343              
344 130           compmethodmeta->name = SvREFCNT_inc(ctx->name);
345 65           compmethodmeta->class = NULL;
346 65           compmethodmeta->is_common = false;
347              
348 65           hv_stores(ctx->moddata, "Class::Plain/compmethodmeta", newSVuv(PTR2UV(compmethodmeta)));
349            
350 65           LEAVE;
351 65           }
352              
353 18           static bool parse_method_filter_attr(pTHX_ struct XSParseSublikeContext *ctx, SV *attr, SV *val, void *hookdata)
354             {
355 18 50         MethodMeta *compmethodmeta = NUM2PTR(MethodMeta *, SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/compmethodmeta", 0)));
356              
357             struct MethodAttributeDefinition *def;
358 18 50         for(def = method_attributes; def->attrname; def++) {
359 18 50         if(!strEQ(SvPVX(attr), def->attrname))
360 0           continue;
361              
362             /* TODO: We might want to wrap the CV in some sort of MethodMeta struct
363             * but for now we'll just pass the XSParseSublikeContext context */
364 18 50         (*def->apply)(aTHX_ compmethodmeta, SvPOK(val) ? SvPVX(val) : NULL, def->applydata);
365              
366 18           return true;
367             }
368              
369             /* No error, just let it fall back to usual attribute handling */
370             return false;
371             }
372              
373 65           static void parse_method_post_blockstart(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
374             {
375 65 50         MethodMeta *compmethodmeta = NUM2PTR(MethodMeta *, SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/compmethodmeta", 0)));
376 65 100         if(compmethodmeta->is_common) {
377 18           IV var_index = pad_add_name_pvs("$class", 0, NULL, NULL);
378 18 50         if (!(var_index == 1)) {
379 0           croak("[Unexpected]Invalid index of the $class variable:%d", (int)var_index);
380             }
381             }
382             else {
383 47           IV var_index = pad_add_name_pvs("$self", 0, NULL, NULL);
384 47 50         if(var_index != 1) {
385 0           croak("[Unexpected]Invalid index of the $self variable:%d", (int)var_index);
386             }
387             }
388              
389 65           intro_my();
390 65           }
391              
392 65           static void parse_method_pre_blockend(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
393             {
394 65 50         MethodMeta *compmethodmeta = NUM2PTR(MethodMeta *, SvUV(*hv_fetchs(ctx->moddata, "Class::Plain/compmethodmeta", 0)));
395              
396             /* If we have no ctx->body that means this was a bodyless method
397             * declaration; a required method
398             */
399 65 100         if (ctx->body) {
400 61 100         if(compmethodmeta->is_common) {
401 18           ctx->body = op_append_list(OP_LINESEQ,
402             ClassPlain_newCOMMONMETHSTARTOP(aTHX_ 0 |
403             (0)),
404             ctx->body);
405             }
406             else {
407             OP *fieldops = NULL, *methstartop;
408 43           fieldops = op_append_list(OP_LINESEQ, fieldops,
409             newSTATEOP(0, NULL, NULL));
410 43           fieldops = op_append_list(OP_LINESEQ, fieldops,
411             (methstartop = ClassPlain_newMETHSTARTOP(aTHX_ 0 |
412             (0) |
413             (0))));
414              
415 43           ctx->body = op_append_list(OP_LINESEQ, fieldops, ctx->body);
416             }
417             }
418 65           }
419              
420 65           static void parse_method_post_newcv(pTHX_ struct XSParseSublikeContext *ctx, void *hookdata)
421             {
422             MethodMeta *compmethodmeta;
423             {
424 65           SV *tmpsv = *hv_fetchs(ctx->moddata, "Class::Plain/compmethodmeta", 0);
425 65 50         compmethodmeta = NUM2PTR(MethodMeta *, SvUV(tmpsv));
426 65           sv_setuv(tmpsv, 0);
427             }
428              
429 65 100         if(ctx->cv)
430 61           CvMETHOD_on(ctx->cv);
431              
432 65 100         if(ctx->cv && ctx->name && (ctx->actions & XS_PARSE_SUBLIKE_ACTION_INSTALL_SYMBOL)) {
    100          
    50          
433 58           MethodMeta *meta = ClassPlain_class_add_method(aTHX_ compclass_meta, ctx->name);
434              
435 58           meta->is_common = compmethodmeta->is_common;
436             }
437              
438 65           SvREFCNT_dec(compmethodmeta->name);
439 65           Safefree(compmethodmeta);
440 65           }
441              
442             static struct XSParseSublikeHooks parse_method_hooks = {
443             .flags = XS_PARSE_SUBLIKE_FLAG_FILTERATTRS |
444             XS_PARSE_SUBLIKE_COMPAT_FLAG_DYNAMIC_ACTIONS |
445             XS_PARSE_SUBLIKE_FLAG_BODY_OPTIONAL,
446             .permit_hintkey = "Class::Plain/method",
447             .permit = parse_method_permit,
448             .pre_subparse = parse_method_pre_subparse,
449             .filter_attr = parse_method_filter_attr,
450             .post_blockstart = parse_method_post_blockstart,
451             .pre_blockend = parse_method_pre_blockend,
452             .post_newcv = parse_method_post_newcv,
453             };
454              
455             /* internal function shared by various *.c files */
456 0           void ClassPlain_need_PLparser(pTHX)
457             {
458 0 0         if(!PL_parser) {
459             /* We need to generate just enough of a PL_parser to keep newSTATEOP()
460             * happy, otherwise it will SIGSEGV (RT133258)
461             */
462 0           SAVEVPTR(PL_parser);
463 0           Newxz(PL_parser, 1, yy_parser);
464 0           SAVEFREEPV(PL_parser);
465              
466 0           PL_parser->copline = NOLINE;
467             }
468 0           }
469              
470             MODULE = Class::Plain PACKAGE = Class::Plain::MetaFunctions
471              
472             BOOT:
473 7           XopENTRY_set(&xop_methstart, xop_name, "methstart");
474 7           XopENTRY_set(&xop_methstart, xop_desc, "enter method");
475 7           XopENTRY_set(&xop_methstart, xop_class, OA_BASEOP);
476 7           Perl_custom_op_register(aTHX_ &pp_methstart, &xop_methstart);
477              
478 7           XopENTRY_set(&xop_commonmethstart, xop_name, "commonmethstart");
479 7           XopENTRY_set(&xop_commonmethstart, xop_desc, "enter method :common");
480 7           XopENTRY_set(&xop_commonmethstart, xop_class, OA_BASEOP);
481 7           Perl_custom_op_register(aTHX_ &pp_commonmethstart, &xop_commonmethstart);
482              
483 7           boot_xs_parse_keyword(0.22); /* XPK_AUTOSEMI */
484            
485             register_xs_parse_keyword("class", &kwhooks_class, (void *)0);
486              
487             register_xs_parse_keyword("field", &kwhooks_field, "field");
488            
489 7           boot_xs_parse_sublike(0.15); /* dynamic actions */
490              
491             register_xs_parse_sublike("method", &parse_method_hooks, (void *)0);