Initial prototype
[p5sagit/Gather-Once.git] / Once.xs
CommitLineData
14342f72 1#include "EXTERN.h"
2#include "perl.h"
3#include "callchecker0.h"
4#include "callparser.h"
5#include "XSUB.h"
6
7typedef struct payload_St {
8 bool topicalise;
9 CV *predicate_cv;
10} payload_t;
11
12#define SVt_PADNAME SVt_PVMG
13
14#ifndef COP_SEQ_RANGE_LOW_set
15# define COP_SEQ_RANGE_LOW_set(sv,val) \
16 do { ((XPVNV*)SvANY(sv))->xnv_u.xpad_cop_seq.xlow = val; } while(0)
17# define COP_SEQ_RANGE_HIGH_set(sv,val) \
18 do { ((XPVNV*)SvANY(sv))->xnv_u.xpad_cop_seq.xhigh = val; } while(0)
19#endif /* !COP_SEQ_RANGE_LOW_set */
20
21#ifndef PERL_PADSEQ_INTRO
22# define PERL_PADSEQ_INTRO I32_MAX
23#endif /* !PERL_PADSEQ_INTRO */
24
25#define pad_add_my_scalar_pvn(namepv, namelen) \
26 THX_pad_add_my_scalar_pvn(aTHX_ namepv, namelen)
27static PADOFFSET
28THX_pad_add_my_scalar_pvn(pTHX_ char const *namepv, STRLEN namelen)
29{
30 PADOFFSET offset;
31 SV *namesv, *myvar;
32 myvar = *av_fetch(PL_comppad, AvFILLp(PL_comppad) + 1, 1);
33 offset = AvFILLp(PL_comppad);
34 SvPADMY_on(myvar);
35 PL_curpad = AvARRAY(PL_comppad);
36 namesv = newSV_type(SVt_PADNAME);
37 sv_setpvn(namesv, namepv, namelen);
38 COP_SEQ_RANGE_LOW_set(namesv, PL_cop_seqmax);
39 COP_SEQ_RANGE_HIGH_set(namesv, PERL_PADSEQ_INTRO);
40 PL_cop_seqmax++;
41 av_store(PL_comppad_name, offset, namesv);
42 return offset;
43}
44
45#define DEMAND_IMMEDIATE 0x00000001
46#define DEMAND_NOCONSUME 0x00000002
47#define demand_unichar(c, f) THX_demand_unichar(aTHX_ c, f)
48static void
49THX_demand_unichar (pTHX_ I32 c, U32 flags)
50{
51 if(!(flags & DEMAND_IMMEDIATE))
52 lex_read_space(0);
53
54 if(lex_peek_unichar(0) != c)
55 croak("syntax error");
56
57 if(!(flags & DEMAND_NOCONSUME))
58 lex_read_unichar(0);
59}
60
61#define CXt_MOO 12
62#define CXt_KOOH 13
63
64static OP *
65pp_entertake (pTHX)
66{
67 dSP;
68 register PERL_CONTEXT *cx;
69 const I32 gimme = GIMME_V;
70
71 if ((0 == (PL_op->op_flags & OPf_SPECIAL)) && !SvTRUEx(POPs))
72 RETURNOP(cLOGOP->op_other->op_next);
73
74 ENTER;
75
76 PUSHBLOCK(cx, CXt_KOOH, SP);
77 cx->blk_givwhen.leave_op = cLOGOP->op_other;
78
79 RETURN;
80}
81
82STATIC I32
83S_dopoptogather(pTHX_ I32 startingblock)
84{
85 dVAR;
86 I32 i;
87 for (i = startingblock; i >= 0; i--) {
88 register const PERL_CONTEXT *cx = &cxstack[i];
89 switch (CxTYPE(cx)) {
90 default:
91 continue;
92 case CXt_MOO:
93 DEBUG_l( Perl_deb(aTHX_ "(dopoptogiven(): found given at cx=%ld)\n", (long)i));
94 return i;
95
96 case CXt_LOOP_PLAIN:
97 assert(!CxFOREACHDEF(cx));
98 break;
99 case CXt_LOOP_LAZYIV:
100 case CXt_LOOP_LAZYSV:
101 case CXt_LOOP_FOR:/* FIXME */
102 if (CxFOREACHDEF(cx)) {
103 DEBUG_l( Perl_deb(aTHX_ "(dopoptogiven(): found foreach at cx=%ld)\n", (long)i));
104 return i;
105 }
106 }
107 }
108 return i;
109}
110
111static OP *
112pp_leavetake (pTHX)
113{
114 dSP;
115 I32 cxix;
116 register PERL_CONTEXT *cx;
117 I32 gimme;
118 SV **newsp;
119 PMOP *newpm;
120
121 cxix = S_dopoptogather(aTHX_ cxstack_ix);
122 if (cxix < 0)
123 /* diag_listed_as: Can't "when" outside a topicalizer */
124 DIE(aTHX_ "Can't \"%s\" outside a topicalizer",
125 PL_op->op_flags & OPf_SPECIAL ? "default" : "when");
126
127 POPBLOCK(cx,newpm);
128 assert(CxTYPE(cx) == CXt_KOOH);
129
130 LEAVE;
131
132 if (cxix < cxstack_ix)
133 dounwind(cxix);
134
135 cx = &cxstack[cxix];
136
137 RETURNOP(cx->blk_givwhen.leave_op);
138}
139
140static OP *
141myparse_args_take (pTHX_ GV *namegv, SV *args, U32 *flagsp)
142{
143 OP *condop, *blkop, *leaveop;
144 LOGOP *enterop;
145 SV *predicate_cv_ref;
146 int blk_floor;
147 bool topicalise;
148
149 PERL_UNUSED_ARG(namegv);
150 PERL_UNUSED_ARG(flagsp);
151
152 demand_unichar('(', 0);
153 condop = parse_fullexpr(0);
154 demand_unichar(')', 0);
155
156 demand_unichar('{', DEMAND_NOCONSUME);
157 blk_floor = Perl_block_start(aTHX_ 1);
158 blkop = parse_block(0);
159 blkop = Perl_block_end(aTHX_ blk_floor, blkop);
160
161 NewOp(1101, enterop, 1, LOGOP);
162 enterop->op_type = OP_ENTERWHEN;
163 enterop->op_ppaddr = pp_entertake;
164 enterop->op_flags = OPf_KIDS;
165 enterop->op_targ = -1;
166 enterop->op_private = 0;
167
168 leaveop = newUNOP(OP_LEAVEWHEN, 0, (OP *)enterop);
169 leaveop->op_ppaddr = pp_leavetake;
170
171 topicalise = SvTRUE(*av_fetch((AV *)SvRV(args), 0, 0));
172 if (topicalise) {
173 OP *pvarop;
174
175 pvarop = newOP(OP_PADSV, 0);
176 pvarop->op_targ = pad_findmy("$Gather::Once::current_topic",
177 sizeof("$Gather::Once::current_topic")-1, 0);
178
179 if (pvarop->op_targ == NOT_IN_PAD)
180 croak("outside topicaliser"); /* FIXME */
181
182 condop = op_append_elem(OP_LIST, condop, pvarop);
183 }
184
185 predicate_cv_ref = *av_fetch((AV *)SvRV(args), 1, 0);
186 condop = newUNOP(OP_ENTERSUB, OPf_STACKED,
187 op_append_elem(OP_LIST, condop,
188 newCVREF(0, newSVOP(OP_CONST, 0,
189 predicate_cv_ref))));
190
191 enterop->op_first = condop;
192 enterop->op_first->op_sibling = op_scope(blkop);
193 leaveop->op_next = LINKLIST(enterop->op_first);
194 enterop->op_first->op_next = (OP *)enterop;
195
196 enterop->op_next = LINKLIST(enterop->op_first->op_sibling);
197 enterop->op_first->op_sibling->op_next = enterop->op_other = leaveop;
198
199 return leaveop;
200}
201
202static OP *
203pp_entergather (pTHX)
204{
205 dSP; dTARGET;
206 register PERL_CONTEXT *cx;
207 const I32 gimme = GIMME_V;
208
209 ENTER;
210 if (PL_op->op_targ != NOT_IN_PAD)
211 sv_setsv(TARG, POPs);
212
213 PUSHBLOCK(cx, CXt_MOO, SP);
214 cx->blk_givwhen.leave_op = cLOGOP->op_other;
215
216 RETURN;
217}
218
219static OP *
220pp_leavegather (pTHX)
221{
222 dSP;
223 register PERL_CONTEXT *cx;
224 I32 gimme;
225 SV **newsp;
226 PMOP *newpm;
227
228 POPBLOCK(cx,newpm);
229 assert(CxTYPE(cx) == CXt_MOO);
230
231 //SP = adjust_stack_on_leave(newsp, SP, newsp, gimme, SVs_PADTMP|SVs_TEMP);
232 //PL_curpm = newpm; /* Don't pop $1 et al till now */
233
234 LEAVE;
235 RETURN;
236}
237
238static OP *
239myparse_args_gather (pTHX_ GV *namegv, SV *topicalise, U32 *flagsp)
240{
241 OP *topicaliser, *blkop, *initop = NULL;
242 int blk_floor;
243
244 PERL_UNUSED_ARG(namegv);
245 PERL_UNUSED_ARG(flagsp);
246
247 if (SvTRUE(topicalise)) {
248 demand_unichar('(', 0);
249 topicaliser = parse_fullexpr(0);
250 demand_unichar(')', 0);
251 }
252
253 demand_unichar('{', DEMAND_NOCONSUME);
254 blk_floor = Perl_block_start(aTHX_ 1);
255 if (SvTRUE(topicalise)) {
256 initop = newOP(OP_PADSV, (OPpLVAL_INTRO<<8));
257 initop->op_targ = pad_add_my_scalar_pvn("$Gather::Once::current_topic",
258 sizeof("$Gather::Once::current_topic")-1);
259 }
260
261 blkop = parse_block(0);
262 if (initop)
263 blkop = op_prepend_elem(OP_LINESEQ, initop, blkop);
264
265 blkop = Perl_block_end(aTHX_ blk_floor, blkop);
266
267 LOGOP *enterop;
268 NewOp(1101, enterop, 1, LOGOP);
269 enterop->op_type = OP_ENTERGIVEN;
270 enterop->op_ppaddr = pp_entergather;
271 enterop->op_flags = OPf_KIDS;
272 enterop->op_targ = SvTRUE(topicalise) ? initop->op_targ : NOT_IN_PAD;
273 enterop->op_private = 0;
274
275 OP *leaveop;
276 leaveop = newUNOP(OP_LEAVEGIVEN, 0, (OP *)enterop);
277 leaveop->op_ppaddr = pp_leavegather;
278
279 enterop->op_first = SvTRUE(topicalise) ? topicaliser : newOP(OP_NULL, 0);
280 enterop->op_first->op_sibling = op_scope(blkop);
281 leaveop->op_next = LINKLIST(enterop->op_first);
282 enterop->op_first->op_next = (OP *)enterop;
283
284 enterop->op_next = LINKLIST(enterop->op_first->op_sibling);
285 enterop->op_first->op_sibling->op_next = enterop->op_other = leaveop;
286
287 return leaveop;
288}
289
290static OP *
291myck_gathertake (pTHX_ OP *entersubop, GV *namegv, SV *ckobj)
292{
293 OP *rv2cvop, *pushop, *blkop;
294
295 PERL_UNUSED_ARG(namegv);
296 PERL_UNUSED_ARG(ckobj);
297
298 pushop = cUNOPx(entersubop)->op_first;
299 if (!pushop->op_sibling)
300 pushop = cUNOPx(pushop)->op_first;
301
302 blkop = pushop->op_sibling;
303
304 rv2cvop = blkop->op_sibling;
305 blkop->op_sibling = NULL;
306 pushop->op_sibling = rv2cvop;
307 op_free(entersubop);
308
309 return blkop;
310}
311
312MODULE = Gather::Once PACKAGE = Gather::Once
313
314void
315setup_gather_hook (CV *gather_cv, SV *topicalise)
316 CODE:
317 cv_set_call_parser(gather_cv, myparse_args_gather, topicalise);
318 cv_set_call_checker(gather_cv, myck_gathertake, &PL_sv_undef);
319
320void
321setup_take_hook (CV *take_cv, SV *args)
322 CODE:
323 cv_set_call_parser(take_cv, myparse_args_take, args);
324 cv_set_call_checker(take_cv, myck_gathertake, &PL_sv_undef);