File Coverage

lib/Object/Pad/Keyword/Accessor.xs
Criterion Covered Total %
statement 87 98 88.7
branch 29 58 50.0
condition n/a
subroutine n/a
pod n/a
total 116 156 74.3


line stmt bran cond sub pod time code
1             /* You may distribute under the terms of either the GNU General Public License
2             * or the Artistic License (the same terms as Perl itself)
3             *
4             * (C) Paul Evans, 2022-2023 -- leonerd@leonerd.org.uk
5             */
6             #define PERL_NO_GET_CONTEXT
7              
8             #include "EXTERN.h"
9             #include "perl.h"
10             #include "XSUB.h"
11              
12             #include "XSParseKeyword.h"
13             #include "object_pad.h"
14              
15             #ifndef newSVsv_nomg
16             static SV *S_newSVsv_nomg(pTHX_ SV *osv)
17             {
18 1           SV *nsv = newSV(0);
19 1           sv_setsv_nomg(nsv, osv);
20             return nsv;
21             }
22              
23             # define newSVsv_nomg(osv) S_newSVsv_nomg(aTHX_ (osv))
24             #endif
25              
26             struct AccessorCtx {
27             CV *getcv;
28             CV *setcv;
29             };
30              
31 1           static int accessor_magic_get(pTHX_ SV *sv, MAGIC *mg)
32             {
33 1           struct AccessorCtx *ctx = (struct AccessorCtx *)mg->mg_ptr;
34 1           SV *self = mg->mg_obj;
35              
36 1           dSP;
37              
38 1           ENTER;
39 1           SAVETMPS;
40              
41 1 50         PUSHMARK(SP);
42 1 50         EXTEND(SP, 1);
43 1           PUSHs(self);
44 1           PUTBACK;
45              
46 1           int count = call_sv((SV *)ctx->getcv, G_SCALAR);
47             PERL_UNUSED_VAR(count);
48             assert(count == 1);
49              
50 1           SPAGAIN;
51              
52 1           sv_setsv_nomg(sv, POPs);
53              
54 1           PUTBACK;
55 1 50         FREETMPS;
56 1           LEAVE;
57              
58 1           return 1;
59             }
60              
61 1           static int accessor_magic_set(pTHX_ SV *sv, MAGIC *mg)
62             {
63 1           struct AccessorCtx *ctx = (struct AccessorCtx *)mg->mg_ptr;
64 1           SV *self = mg->mg_obj;
65              
66 1           dSP;
67              
68 1           ENTER;
69 1           SAVETMPS;
70              
71 1 50         PUSHMARK(SP);
72 1 50         EXTEND(SP, 2);
73 1           PUSHs(self);
74 1           mPUSHs(newSVsv_nomg(sv));
75 1           PUTBACK;
76              
77 1           call_sv((SV *)ctx->setcv, G_VOID);
78              
79 1 50         FREETMPS;
80 1           LEAVE;
81              
82 1           return 1;
83             }
84              
85             static MGVTBL vtbl_accessor = {
86             .svt_get = accessor_magic_get,
87             .svt_set = accessor_magic_set,
88             };
89              
90 2           XS_INTERNAL(make_accessor_lvalue)
91             {
92 4           dXSARGS;
93              
94 2 50         if(items < 1 || items > 1)
95 0           croak("Usage: $self->accessor");
96             SP -= items;
97              
98 2           SV *self = ST(0);
99              
100 2           SV *retval = sv_newmortal();
101 2           sv_magicext(retval, SvREFCNT_inc(self), PERL_MAGIC_ext, &vtbl_accessor, XSANY.any_ptr, 0);
102              
103 2           ST(0) = retval;
104              
105 2           XSRETURN(1);
106             }
107              
108             enum {
109             PART_GET = 1,
110             PART_SET,
111             };
112              
113 1           static int build_accessor(pTHX_ OP **out, XSParseKeywordPiece *args[], size_t nargs, void *hookdata)
114             {
115             int argi = 0;
116              
117 1           SV *name = args[argi++]->sv;
118              
119 1           ClassMeta *classmeta = get_compclassmeta();
120              
121             struct AccessorCtx *ctx;
122 1           Newxz(ctx, 1, struct AccessorCtx);
123              
124 1           int nparts = args[argi++]->i;
125 3 100         for(int parti = 0; parti < nparts; parti++) {
126 2           int parttype = args[argi++]->i;
127 2           switch(parttype) {
128             case PART_GET:
129 1 50         if(ctx->getcv)
130 0           croak("Cannot provide two 'get' blocks for %" SVf " accessor", SVfARG(name));
131 1           ctx->getcv = cv_clone((CV *)args[argi++]->sv);
132             assert(SvTYPE(ctx->getcv) == SVt_PVCV);
133 1           break;
134              
135             case PART_SET:
136 1 50         if(ctx->setcv)
137 0           croak("Cannot provide two 'set' blocks for %" SVf " accessor", SVfARG(name));
138 1           ctx->setcv = cv_clone((CV *)args[argi++]->sv);
139             assert(SvTYPE(ctx->setcv) == SVt_PVCV);
140 1           break;
141              
142             default:
143 0           croak("TODO: Handle part type %d", parttype);
144             }
145             }
146              
147             /* Sanity checking */
148 1 50         if(!ctx->getcv)
149 0           croak("accessor needs a 'get' stage");
150 1 50         if(!ctx->setcv)
151 0           croak("accessor needs a 'set' stage");
152              
153 1           CV *cv = newXS(NULL, make_accessor_lvalue, __FILE__);
154 1           CvMETHOD_on(cv);
155 1           CvLVALUE_on(cv);
156 1           CvXSUBANY(cv).any_ptr = ctx;
157              
158 1           mop_class_add_method_cv(classmeta, name, cv);
159              
160 1           return KEYWORD_PLUGIN_STMT;
161             }
162              
163             /* stolen from perl-additions.c.inc */
164             #define lex_consume_unichar(c) MY_lex_consume_unichar(aTHX_ c)
165             static bool MY_lex_consume_unichar(pTHX_ U32 c)
166             {
167 2 50         if(lex_peek_unichar(0) != c)
    50          
168             return FALSE;
169              
170 2           lex_read_unichar(0);
171             return TRUE;
172             }
173              
174             #define HINTKEY_PADIX "Object::Pad::Keyword::Accessor/var-padix"
175              
176 1           static void anonmethod_set_start(pTHX_ void *hookdata)
177             {
178 1 50         if(!lex_consume_unichar('('))
179             return;
180 1           lex_read_space(0);
181              
182 1           char *name = PL_parser->bufptr;
183              
184 1 50         if(lex_read_unichar(0) != '$')
185 0           croak("Expected a scalar lexical name");
186              
187 1 50         if(!isIDFIRST_uni(lex_read_unichar(0)))
    50          
    50          
    50          
188 0           croak("Expected a scalar lexical name");
189 1 50         while(isIDCONT_uni(lex_peek_unichar(0)))
    50          
    50          
    50          
190 0           lex_read_unichar(0);
191              
192 1           STRLEN namelen = PL_parser->bufptr - name;
193              
194 1 50         if(namelen == 2 && name[1] == '_')
    0          
195 0           croak("Can't use global $_ in \"my\"");
196              
197 1           PADOFFSET padix = pad_add_name_pvn(name, namelen, 0, NULL, NULL);
198 1           hv_stores(GvHV(PL_hintgv), HINTKEY_PADIX, newSVuv(padix));
199              
200 1 50         if(!lex_consume_unichar(')'))
201 0           croak("Expected ')'");
202              
203 1           intro_my();
204             }
205              
206 1           static OP *anonmethod_set_end(pTHX_ OP *o, void *hookdata)
207             {
208 1           SV **svp = hv_fetchs(GvHV(PL_hintgv), HINTKEY_PADIX, 0);
209 1 50         if(!svp)
210             return o;
211              
212             /* $var = $_[0]; */
213             OP *padsvop;
214 1           OP *setupop = newBINOP(OP_SASSIGN, 0,
215             newGVOP(OP_AELEMFAST, 0 << 8, PL_defgv),
216             padsvop = newOP(OP_PADSV, 0));
217 1 50         padsvop->op_targ = SvUV(*svp);
218              
219 1           o = op_append_elem(OP_LINESEQ, setupop, o);
220              
221 1           return o;
222             }
223              
224             static const struct XSParseKeywordHooks kwhooks_accessor = {
225             .permit_hintkey = "Object::Pad::Keyword::Accessor",
226              
227             .pieces = (const struct XSParseKeywordPieceType []) {
228             XPK_IDENT,
229             XPK_BRACES(
230             XPK_REPEATED(
231             XPK_TAGGEDCHOICE(
232             /* A `get` block is just a regular anon method */
233             XPK_SEQUENCE(XPK_KEYWORD("get"), OPXPK_ANONMETHOD),
234             XPK_TAG(PART_GET),
235             /* A `set` block requires special parsing of the "($var)" syntax */
236             XPK_SEQUENCE(XPK_KEYWORD("set"), XPK_STAGED_ANONSUB(
237             OPXPK_ANONMETHOD_PREPARE,
238             OPXPK_ANONMETHOD_START,
239             /* TODO: This is rather hacky; using a code block to do some
240             * parsing. Ideally we'd like to use
241             * XPK_PARENS(XPK_LEXVAR_MY(XPK_LEXVAR_SCALAR))
242             * for it, but that leaves us not knowing the padix for the new
243             * variable when we come to END+WRAP the method into a CV. We'd
244             * need some way to interrupt and put more code in there.
245             * Somehow.
246             */
247             XPK_ANONSUB_START(&anonmethod_set_start),
248             XPK_ANONSUB_END(&anonmethod_set_end),
249             OPXPK_ANONMETHOD_WRAP)),
250             XPK_TAG(PART_SET)
251             )
252             )
253             ),
254             {0}
255             },
256             .build = &build_accessor,
257             };
258              
259             MODULE = Object::Pad::Keyword::Accessor PACKAGE = Object::Pad::Keyword::Accessor
260              
261             BOOT:
262 2           boot_xs_parse_keyword(0.35);
263              
264             /* TODO: Consider if this needs to be done via O:P directly */
265             register_xs_parse_keyword("accessor", &kwhooks_accessor, NULL);