File Coverage

lib/Object/Pad/Keyword/Accessor.xs
Criterion Covered Total %
statement 91 102 89.2
branch 29 60 48.3
condition n/a
subroutine n/a
pod n/a
total 120 162 74.0


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             SV *nsv = newSV(0);
19             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 2 50         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 4 50         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 1           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 1           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 0           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 2           static bool MY_lex_consume_unichar(pTHX_ U32 c)
166             {
167 2 50         if(lex_peek_unichar(0) != c)
168             return FALSE;
169              
170 2           lex_read_unichar(0);
171 2           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             U32 c;
188 1 50         if(!(c = lex_read_unichar(0)) || !isIDFIRST_uni(c))
    50          
    50          
    50          
    0          
189 0           croak("Expected a scalar lexical name");
190 3 50         while((c = lex_peek_unichar(0)) && isIDCONT_uni(c))
    50          
    100          
    0          
191 2           lex_read_unichar(0);
192              
193 1           STRLEN namelen = PL_parser->bufptr - name;
194              
195 1 50         if(namelen == 2 && name[1] == '_')
    0          
196 0           croak("Can't use global $_ in \"my\"");
197              
198 1           PADOFFSET padix = pad_add_name_pvn(name, namelen, 0, NULL, NULL);
199 1           hv_stores(GvHV(PL_hintgv), HINTKEY_PADIX, newSVuv(padix));
200              
201 1 50         if(!lex_consume_unichar(')'))
202 0           croak("Expected ')'");
203              
204 1           intro_my();
205             }
206              
207 1           static OP *anonmethod_set_end(pTHX_ OP *o, void *hookdata)
208             {
209 1           SV **svp = hv_fetchs(GvHV(PL_hintgv), HINTKEY_PADIX, 0);
210 1 50         if(!svp)
211             return o;
212              
213             /* $var = $_[0]; */
214             OP *padsvop;
215 1           OP *setupop = newBINOP(OP_SASSIGN, 0,
216             newGVOP(OP_AELEMFAST, 0 << 8, PL_defgv),
217             padsvop = newOP(OP_PADSV, 0));
218 1           padsvop->op_targ = SvUV(*svp);
219              
220 1           o = op_append_elem(OP_LINESEQ, setupop, o);
221              
222 1           return o;
223             }
224              
225             static const struct XSParseKeywordHooks kwhooks_accessor = {
226             .permit_hintkey = "Object::Pad::Keyword::Accessor",
227              
228             .pieces = (const struct XSParseKeywordPieceType []) {
229             XPK_IDENT,
230             XPK_BRACES(
231             XPK_REPEATED(
232             XPK_TAGGEDCHOICE(
233             /* A `get` block is just a regular anon method */
234             XPK_SEQUENCE(XPK_KEYWORD("get"), OPXPK_ANONMETHOD),
235             XPK_TAG(PART_GET),
236             /* A `set` block requires special parsing of the "($var)" syntax */
237             XPK_SEQUENCE(XPK_KEYWORD("set"), XPK_STAGED_ANONSUB(
238             OPXPK_ANONMETHOD_PREPARE,
239             OPXPK_ANONMETHOD_START,
240             /* TODO: This is rather hacky; using a code block to do some
241             * parsing. Ideally we'd like to use
242             * XPK_PARENS(XPK_LEXVAR_MY(XPK_LEXVAR_SCALAR))
243             * for it, but that leaves us not knowing the padix for the new
244             * variable when we come to END+WRAP the method into a CV. We'd
245             * need some way to interrupt and put more code in there.
246             * Somehow.
247             */
248             XPK_ANONSUB_START(&anonmethod_set_start),
249             XPK_ANONSUB_END(&anonmethod_set_end),
250             OPXPK_ANONMETHOD_WRAP)),
251             XPK_TAG(PART_SET)
252             )
253             )
254             ),
255             {0}
256             },
257             .build = &build_accessor,
258             };
259              
260             MODULE = Object::Pad::Keyword::Accessor PACKAGE = Object::Pad::Keyword::Accessor
261              
262             BOOT:
263 2           boot_xs_parse_keyword(0.35);
264              
265             /* TODO: Consider if this needs to be done via O:P directly */
266 2           register_xs_parse_keyword("accessor", &kwhooks_accessor, NULL);