File Coverage

src/constraints.c
Criterion Covered Total %
statement 460 478 96.2
branch 284 320 88.7
condition n/a
subroutine n/a
pod n/a
total 744 798 93.2


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, 2024-2025 -- leonerd@leonerd.org.uk
5             */
6              
7             #include "EXTERN.h"
8             #include "perl.h"
9              
10             #include "constraints.h"
11              
12             #include "perl-backcompat.c.inc"
13              
14             #include "make_argcheck_ops.c.inc"
15             #include "newOP_CUSTOM.c.inc"
16             #include "optree-additions.c.inc"
17             #include "sv_regexp_match.c.inc"
18             #include "sv_streq.c.inc"
19             #include "sv_numcmp.c.inc"
20              
21             #include "ckcall_constfold.c.inc"
22              
23             #if HAVE_PERL_VERSION(5, 28, 0)
24             /* perl 5.28.0 onward can do gv_fetchmeth superclass lookups without caching
25             */
26             # define HAVE_FETCHMETH_SUPER_NOCACHE
27             #endif
28              
29             #define newSVsv_num(osv) S_newSVsv_num(aTHX_ osv)
30 90           static SV *S_newSVsv_num(pTHX_ SV *osv)
31             {
32 90 50         if(SvNOK(osv))
33 0           return newSVnv(SvNV(osv));
34 90 50         if(SvIOK(osv) && SvIsUV(osv))
    50          
35 0           return newSVuv(SvUV(osv));
36              
37 90           return newSViv(SvIV(osv));
38             }
39              
40             #define newSVsv_str(osv) S_newSVsv_str(aTHX_ osv)
41 22           static SV *S_newSVsv_str(pTHX_ SV *osv)
42             {
43 22           SV *nsv = newSV(0);
44 22           sv_copypv(nsv, osv);
45 22           return nsv;
46             }
47              
48             #if !HAVE_PERL_VERSION(5, 32, 0)
49             # define sv_isa_sv(sv, namesv) S_sv_isa_sv(aTHX_ sv, namesv)
50             static bool S_sv_isa_sv(pTHX_ SV *sv, SV *namesv)
51             {
52             if(!SvROK(sv) || !SvOBJECT(SvRV(sv)))
53             return FALSE;
54              
55             /* TODO: ->isa invocation */
56              
57             return sv_derived_from_sv(sv, namesv, 0);
58             }
59             #endif
60              
61             #ifndef op_force_list
62             # define op_force_list(o) S_op_force_list(aTHX_ o)
63             static OP *S_op_force_list(pTHX_ OP *o)
64             /* Sufficiently good enough for our purposes */
65             {
66             op_null(o);
67             return o;
68             }
69             #endif
70              
71             #define alloc_constraint(svp, constraintp, func, n) S_alloc_constraint(aTHX_ svp, constraintp, func, n)
72 196           static void S_alloc_constraint(pTHX_ SV **svp, struct Constraint **constraintp, ConstraintFunc *func, size_t n)
73             {
74 196           HV *constraint_stash = gv_stashpvs("Data::Checks::Constraint", GV_ADD);
75              
76 196           SV *sv = newSV(sizeof(struct Constraint) + n*sizeof(SV *));
77 196           SvPOK_on(sv);
78 196           struct Constraint *constraint = (struct Constraint *)SvPVX(sv);
79 196           *constraint = (struct Constraint){
80             .func = func,
81             .n = n,
82             };
83              
84 353 100         for(int i = 0; i < n; i++)
85 157           constraint->args[i] = NULL;
86              
87 196           *svp = sv_bless(newRV_noinc(sv), constraint_stash);
88 196           *constraintp = constraint;
89 196           }
90              
91 102           SV *DataChecks_extract_constraint(pTHX_ SV *sv)
92             {
93 102 50         if(!sv_isa(sv, "Data::Checks::Constraint"))
94 0           croak("Expected a Constraint instance as argument");
95              
96 102           return SvRV(sv);
97             }
98              
99             #define sv_has_overload(sv, method) S_sv_has_overload(aTHX_ sv, method)
100 16           static bool S_sv_has_overload(pTHX_ SV *sv, int method)
101             {
102             assert(SvROK(sv));
103              
104 16           HV *stash = SvSTASH(SvRV(sv));
105 16 50         if(!stash || !Gv_AMG(stash))
    50          
    50          
    50          
    0          
    100          
106 9           return false;
107              
108 7           MAGIC *mg = mg_find((const SV *)stash, PERL_MAGIC_overload_table);
109 7 50         if(!mg)
110             return false;
111              
112             CV **cvp = NULL;
113 7 50         if(AMT_AMAGIC((AMT *)mg->mg_ptr))
114 7           cvp = ((AMT *)mg->mg_ptr)->table;
115             if(!cvp)
116             return false;
117              
118 7           CV *cv = cvp[method];
119 7 100         if(!cv)
120             return false;
121              
122             return true;
123             }
124              
125 200013           static bool constraint_Defined(pTHX_ struct Constraint *c, SV *value)
126             {
127 200013           return SvOK(value);
128             }
129              
130 12           static bool constraint_Object(pTHX_ struct Constraint *c, SV *value)
131             {
132 12 100         return SvROK(value) && SvOBJECT(SvRV(value));
    100          
133             }
134              
135 39           static bool constraint_Str(pTHX_ struct Constraint *c, SV *value)
136             {
137 39 100         if(!SvOK(value))
138             return false;
139              
140 35 100         if(SvROK(value)) {
141 18           SV *rv = SvRV(value);
142 18 100         if(!SvOBJECT(rv))
143             return false;
144              
145 6 100         if(sv_has_overload(value, string_amg))
146             return true;
147              
148             return false;
149             }
150             else {
151             return true;
152             }
153             }
154              
155 7           static bool constraint_StrEq(pTHX_ struct Constraint *c, SV *value)
156             {
157 7 100         if(!constraint_Str(aTHX_ c, value))
158             return false;
159              
160 6           SV *strs = c->args[0];
161 6 100         if(SvTYPE(strs) != SVt_PVAV)
162 3           return sv_streq(value, strs);
163              
164             /* TODO: If we were to sort the values initially we could binary-search
165             * these much faster
166             */
167 3           size_t n = av_count((AV *)strs);
168 3           SV **svp = AvARRAY(strs);
169 8 100         for(size_t i = 0; i < n; i++)
170 7 100         if(sv_streq(value, svp[i]))
171             return true;
172              
173             return false;
174             }
175              
176 9           static SV *mk_constraint_StrEq(pTHX_ size_t nargs, SV **args)
177             {
178             SV *ret;
179             struct Constraint *c;
180 9           alloc_constraint(&ret, &c, &constraint_StrEq, 1);
181 9           sv_2mortal(ret);
182              
183 9 50         if(!nargs)
184 0           croak("Require at least one string for StrEq()");
185              
186 9 100         if(nargs == 1)
187             /* We can just store a single string directly */
188 5           c->args[0] = newSVsv_str(args[0]);
189             else {
190 4           AV *strs = newAV_alloc_x(nargs);
191 15 100         for(size_t i = 0; i < nargs; i++)
192 11           av_store(strs, i, newSVsv_str(args[i]));
193              
194 4           c->args[0] = (SV *)strs;
195             }
196              
197 9           return ret;
198             }
199              
200 9           static bool constraint_StrMatch(pTHX_ struct Constraint *c, SV *value)
201             {
202 9 100         if(!constraint_Str(aTHX_ c, value))
203             return false;
204              
205 4           return sv_regexp_match(value, (REGEXP *)c->args[0]);
206             }
207              
208 2           static SV *mk_constraint_StrMatch(pTHX_ SV *arg0)
209             {
210             SV *ret;
211             struct Constraint *c;
212 2           alloc_constraint(&ret, &c, &constraint_StrMatch, 1);
213 2           sv_2mortal(ret);
214              
215 2 50         if(!SvROK(arg0) || !SvRXOK(SvRV(arg0)))
    50          
216 0           croak("Require a pre-compiled regexp pattern for StrMatch()");
217              
218 2 50         c->args[0] = SvREFCNT_inc(SvRV(arg0));
219              
220 2           return ret;
221             }
222              
223 48           static bool constraint_Num(pTHX_ struct Constraint *c, SV *value)
224             {
225 48 100         if(!SvOK(value))
226             return false;
227              
228 45 100         if(SvROK(value)) {
229 10           SV *rv = SvRV(value);
230 10 100         if(!SvOBJECT(rv))
231             return false;
232              
233 4 100         if(sv_has_overload(value, numer_amg))
234             return true;
235              
236             return false;
237             }
238 35 100         else if(SvPOK(value)) {
239 5 100         if(!looks_like_number(value))
240             return false;
241              
242             // reject NaN
243 3 100         if(SvPVX(value)[0] == 'N' || SvPVX(value)[0] == 'n')
244             return false;
245              
246             return true;
247             }
248             else {
249             // reject NaN
250 30 100         if(SvNOK(value) && Perl_isnan(SvNV(value)))
    100          
251             return false;
252              
253 29           return true;
254             }
255             }
256              
257             enum {
258             NUMBOUND_LOWER_INCLUSIVE = (1<<0),
259             NUMBOUND_UPPER_INCLUSIVE = (1<<1),
260             };
261              
262 27           static bool constraint_NumBound(pTHX_ struct Constraint *c, SV *value)
263             {
264             /* First off it must be a Num */
265 27 100         if(!constraint_Num(aTHX_ c, value))
266             return false;
267              
268 21 100         if(c->args[0]) {
269 15           int cmp = sv_numcmp(c->args[0], value);
270 15 100         if(cmp > 0 || (cmp == 0 && !(c->flags & NUMBOUND_LOWER_INCLUSIVE)))
    100          
    100          
271             return false;
272             }
273              
274 17 100         if(c->args[1]) {
275 14           int cmp = sv_numcmp(value, c->args[1]);
276 14 100         if(cmp > 0 || (cmp == 0 && !(c->flags & NUMBOUND_UPPER_INCLUSIVE)))
    100          
    100          
277             return false;
278             }
279              
280             return true;
281             }
282              
283 8           static SV *mk_constraint_NumGT(pTHX_ SV *arg0)
284             {
285             SV *ret;
286             struct Constraint *c;
287 8           alloc_constraint(&ret, &c, &constraint_NumBound, 2);
288 8           sv_2mortal(ret);
289              
290 8           c->args[0] = newSVsv_num(arg0);
291 8           c->args[1] = NULL;
292              
293 8           return ret;
294             }
295              
296 10           static SV *mk_constraint_NumGE(pTHX_ SV *arg0)
297             {
298             SV *ret;
299             struct Constraint *c;
300 10           alloc_constraint(&ret, &c, &constraint_NumBound, 2);
301 10           sv_2mortal(ret);
302              
303 10           c->flags = NUMBOUND_LOWER_INCLUSIVE;
304 10           c->args[0] = newSVsv_num(arg0);
305 10           c->args[1] = NULL;
306              
307 10           return ret;
308             }
309              
310 9           static SV *mk_constraint_NumLE(pTHX_ SV *arg0)
311             {
312             SV *ret;
313             struct Constraint *c;
314 9           alloc_constraint(&ret, &c, &constraint_NumBound, 2);
315 9           sv_2mortal(ret);
316              
317 9           c->flags = NUMBOUND_UPPER_INCLUSIVE;
318 9           c->args[0] = NULL;
319 9           c->args[1] = newSVsv_num(arg0);
320              
321 9           return ret;
322             }
323              
324 9           static SV *mk_constraint_NumLT(pTHX_ SV *arg0)
325             {
326             SV *ret;
327             struct Constraint *c;
328 9           alloc_constraint(&ret, &c, &constraint_NumBound, 2);
329 9           sv_2mortal(ret);
330              
331 9           c->args[0] = NULL;
332 9           c->args[1] = newSVsv_num(arg0);
333              
334 9           return ret;
335             }
336              
337 7           static SV *mk_constraint_NumRange(pTHX_ SV *arg0, SV *arg1)
338             {
339             SV *ret;
340             struct Constraint *c;
341 7           alloc_constraint(&ret, &c, &constraint_NumBound, 2);
342 7           sv_2mortal(ret);
343              
344 7           c->flags = NUMBOUND_LOWER_INCLUSIVE;
345 7           c->args[0] = newSVsv_num(arg0);
346 7           c->args[1] = newSVsv_num(arg1);
347              
348 7           return ret;
349             }
350              
351 7           static bool constraint_NumEq(pTHX_ struct Constraint *c, SV *value)
352             {
353 7 100         if(!constraint_Num(aTHX_ c, value))
354             return false;
355              
356 6           SV *nums = c->args[0];
357 6 100         if(SvTYPE(nums) != SVt_PVAV)
358 3           return sv_numcmp(value, nums) == 0;
359              
360             /* TODO: If we were to sort the values initially we could binary-search
361             * these much faster
362             */
363 3           size_t n = av_count((AV *)nums);
364 3           SV **svp = AvARRAY(nums);
365 8 100         for(size_t i = 0; i < n; i++)
366 7 100         if(sv_numcmp(value, svp[i]) == 0)
367             return true;
368              
369             return false;
370             }
371              
372 5           static SV *mk_constraint_NumEq(pTHX_ size_t nargs, SV **args)
373             {
374             SV *ret;
375             struct Constraint *c;
376 5           alloc_constraint(&ret, &c, &constraint_NumEq, 1);
377 5           sv_2mortal(ret);
378              
379 5 50         if(!nargs)
380 0           croak("Require at least one number for NumEq()");
381              
382 5 100         if(nargs == 1)
383             /* We can just store a single number directly */
384 3           c->args[0] = newSVsv_num(args[0]);
385             else {
386 2           AV *nums = newAV_alloc_x(nargs);
387 7 100         for(size_t i = 0; i < nargs; i++)
388 5           av_store(nums, i, newSVsv_num(args[i]));
389              
390 2           c->args[0] = (SV *)nums;
391             }
392              
393 5           return ret;
394             }
395              
396 10           static bool constraint_Isa(pTHX_ struct Constraint *c, SV *value)
397             {
398 10           return sv_isa_sv(value, c->args[0]);
399             }
400              
401 4           static SV *mk_constraint_Isa(pTHX_ SV *arg0)
402             {
403             SV *ret;
404             struct Constraint *c;
405 4           alloc_constraint(&ret, &c, &constraint_Isa, 1);
406              
407 4           c->args[0] = newSVsv(arg0);
408              
409 4           return sv_2mortal(ret);
410             }
411              
412 19           static bool constraint_Can(pTHX_ struct Constraint *c, SV *value)
413             {
414             HV *stash;
415 19 100         if(SvROK(value) && SvOBJECT(SvRV(value)))
    100          
416 5           stash = SvSTASH(SvRV(value));
417 14 100         else if(SvOK(value)) {
418 12           stash = gv_stashsv(value, GV_NOADD_NOINIT);
419 12 100         if(!stash)
420             return false;
421             }
422             else
423             return false;
424              
425             /* TODO: we could cache which classes do or don't satisfy the constraints
426             * and store it somewhere, maybe in an HV in ->args[1] or somesuch */
427              
428 7           SV *methods = c->args[0];
429 7 100         size_t nmethods = SvTYPE(methods) == SVt_PVAV ? av_count((AV *)methods) : 1;
430 19 100         for(size_t idx = 0; idx < nmethods; idx++) {
431 13 100         SV *method = SvTYPE(methods) == SVt_PVAV ? AvARRAY((AV *)methods)[idx] : methods;
432 13 100         if(!gv_fetchmeth_sv(stash, method,
433             #ifdef HAVE_FETCHMETH_SUPER_NOCACHE
434             -1,
435             #else
436             0,
437             #endif
438             0))
439             return false;
440             }
441              
442             return true;
443             }
444              
445 3           static SV *mk_constraint_Can(pTHX_ size_t nargs, SV **args)
446             {
447             SV *ret;
448             struct Constraint *c;
449 3           alloc_constraint(&ret, &c, &constraint_Can, 1);
450 3           sv_2mortal(ret);
451              
452 3 50         if(!nargs)
453 0           croak("Require at least one method name for Can()");
454              
455 3 100         if(nargs == 1)
456             /* We can just store a single string directly */
457 1           c->args[0] = newSVsv_str(args[0]);
458             else {
459 2           AV *strs = newAV_alloc_x(nargs);
460 7 100         for(size_t i = 0; i < nargs; i++)
461 5           av_store(strs, i, newSVsv_str(args[i]));
462              
463 2           c->args[0] = (SV *)strs;
464             }
465              
466 3           return ret;
467             }
468              
469 8           static bool constraint_ArrayRef(pTHX_ struct Constraint *c, SV *value)
470             {
471 8 100         if(!SvOK(value) || !SvROK(value))
    100          
472             return false;
473              
474 5           SV *rv = SvRV(value);
475              
476 5 100         if(!SvOBJECT(rv))
477             /* plain ref */
478 3           return SvTYPE(rv) == SVt_PVAV;
479             else
480 2           return sv_has_overload(value, to_av_amg);
481             }
482              
483 8           static bool constraint_HashRef(pTHX_ struct Constraint *c, SV *value)
484             {
485 8 100         if(!SvOK(value) || !SvROK(value))
    100          
486             return false;
487              
488 5           SV *rv = SvRV(value);
489              
490 5 100         if(!SvOBJECT(rv))
491             /* plain ref */
492 3           return SvTYPE(rv) == SVt_PVHV;
493             else
494 2           return sv_has_overload(value, to_hv_amg);
495             }
496              
497 9           static bool constraint_Callable(pTHX_ struct Constraint *c, SV *value)
498             {
499 9 100         if(!SvOK(value) || !SvROK(value))
    100          
500             return false;
501              
502 6           SV *rv = SvRV(value);
503              
504 6 100         if(!SvOBJECT(rv))
505             /* plain ref */
506 4           return SvTYPE(rv) == SVt_PVCV;
507             else
508 2           return sv_has_overload(value, to_cv_amg);
509             }
510              
511 7           static bool constraint_Maybe(pTHX_ struct Constraint *c, SV *value)
512             {
513 7 100         if(!SvOK(value))
514             return true;
515              
516 6           struct Constraint *inner = (struct Constraint *)SvPVX(c->args[0]);
517 6           return (*inner->func)(aTHX_ inner, value);
518             }
519              
520 3           static SV *mk_constraint_Maybe(pTHX_ SV *arg0)
521             {
522 3           SV *inner = extract_constraint(arg0);
523              
524             SV *ret;
525             struct Constraint *c;
526 3           alloc_constraint(&ret, &c, &constraint_Maybe, 1);
527 3           sv_2mortal(ret);
528              
529 3 50         c->args[0] = SvREFCNT_inc(inner);
530              
531 3           return ret;
532             }
533              
534 7           static bool constraint_Any(pTHX_ struct Constraint *c, SV *value)
535             {
536 7           AV *inners = (AV *)c->args[0];
537 7           SV **innersvs = AvARRAY(inners);
538 7           size_t n = av_count(inners);
539              
540 16 100         for(size_t i = 0; i < n; i++) {
541 12           struct Constraint *inner = (struct Constraint *)SvPVX(innersvs[i]);
542 12 100         if((*inner->func)(aTHX_ inner, value))
543             return true;
544             }
545              
546             return false;
547             }
548              
549 8           static SV *mk_constraint_Any(pTHX_ size_t nargs, SV **args)
550             {
551 8 50         if(!nargs)
552 0           croak("Any() requires at least one inner constraint");
553 8 100         if(nargs == 1)
554 1           return args[0];
555              
556 7           AV *inners = newAV();
557 7           sv_2mortal((SV *)inners); // in case of croak during construction
558              
559 21 100         for(size_t i = 0; i < nargs; i++) {
560 14           SV *innersv = extract_constraint(args[i]);
561 14           struct Constraint *inner = (struct Constraint *)SvPVX(innersv);
562              
563 14 100         if(inner->func == &constraint_Any) {
564 2           AV *kidav = (AV *)inner->args[0];
565 2           size_t nkids = av_count(kidav);
566 6 100         for(size_t kidi = 0; kidi < nkids; kidi++) {
567 8 50         av_push(inners, SvREFCNT_inc(AvARRAY(kidav)[kidi]));
568             }
569             }
570             else
571 12           av_push(inners, SvREFCNT_inc(innersv));
572             }
573              
574             SV *ret;
575             struct Constraint *c;
576 7           alloc_constraint(&ret, &c, &constraint_Any, 1);
577 7           sv_2mortal(ret);
578              
579 7 50         c->args[0] = SvREFCNT_inc(inners);
580              
581 7           return ret;
582             }
583              
584 7           static bool constraint_All(pTHX_ struct Constraint *c, SV *value)
585             {
586 7           AV *inners = (AV *)c->args[0];
587 7 50         if(!inners)
588             return true;
589              
590 0           SV **innersvs = AvARRAY(inners);
591 0           size_t n = av_count(inners);
592              
593 0 0         for(size_t i = 0; i < n; i++) {
594 0           struct Constraint *inner = (struct Constraint *)SvPVX(innersvs[i]);
595 0 0         if(!(*inner->func)(aTHX_ inner, value))
596             return false;
597             }
598              
599             return true;
600             }
601              
602 22           static SV *mk_constraint_All(pTHX_ size_t nargs, SV **args)
603             {
604             /* nargs == 0 is valid */
605 22 100         if(nargs == 1)
606 1           return args[0];
607              
608             AV *inners = NULL;
609 21 100         if(nargs) {
610 19           inners = newAV();
611 19           sv_2mortal((SV *)inners); // in case of croak during construction
612              
613             /* However many NumBound constraints are in 'inners' it's always possible to
614             * optimise them down into just one
615             */
616 19           struct Constraint *all_nums = NULL;
617             SV *all_nums_sv;
618              
619 58 100         for(size_t i = 0; i < nargs; i++) {
620 39           SV *innersv = extract_constraint(args[i]);
621 39           struct Constraint *inner = (struct Constraint *)SvPVX(innersv);
622              
623 39 100         if(inner->func == &constraint_All) {
624 1           AV *kidav = (AV *)inner->args[0];
625 1           size_t nkids = av_count(kidav);
626 3 100         for(size_t kidi = 0; kidi < nkids; kidi++) {
627 4 50         av_push(inners, SvREFCNT_inc(AvARRAY(kidav)[kidi]));
628             }
629             }
630 38 100         else if(inner->func == &constraint_NumBound) {
631 32 100         if(!all_nums) {
632 16           alloc_constraint(&all_nums_sv, &all_nums, &constraint_NumBound, 2);
633 16           av_push(inners, SvRV(all_nums_sv)); /* no SvREFCNT_inc() */
634             }
635 32           SV *innerL = inner->args[0],
636 32           *innerU = inner->args[1];
637              
638             int cmp;
639              
640 32 100         if(innerL) {
641 18 100         if(!all_nums->args[0] || (cmp = sv_numcmp(all_nums->args[0], innerL)) < 0) {
    100          
642 17           SvREFCNT_dec(all_nums->args[0]);
643 17           all_nums->args[0] = newSVsv_num(innerL);
644 17           all_nums->flags = (all_nums->flags & ~NUMBOUND_LOWER_INCLUSIVE)
645 17           | (inner->flags & NUMBOUND_LOWER_INCLUSIVE);
646             }
647 1 50         else if(cmp == 0 && !(inner->flags & NUMBOUND_LOWER_INCLUSIVE))
    50          
648 1           all_nums->flags &= ~NUMBOUND_LOWER_INCLUSIVE;
649             }
650 32 100         if(innerU) {
651 18 100         if(!all_nums->args[1] || (cmp = sv_numcmp(all_nums->args[1], innerU)) > 0) {
    100          
652 15           SvREFCNT_dec(all_nums->args[1]);
653 15           all_nums->args[1] = newSVsv_num(innerU);
654 15           all_nums->flags = (all_nums->flags & ~NUMBOUND_UPPER_INCLUSIVE)
655 15           | (inner->flags & NUMBOUND_UPPER_INCLUSIVE);
656             }
657 3 100         else if(cmp == 0 && !(inner->flags & NUMBOUND_UPPER_INCLUSIVE))
    50          
658 1           all_nums->flags &= ~NUMBOUND_UPPER_INCLUSIVE;
659             }
660             }
661             else
662 6           av_push(inners, SvREFCNT_inc(innersv));
663             }
664              
665             /* it's possible we've now squashed all the Num* bounds into a single one
666             * and nothing else is left */
667 19 100         if(all_nums_sv && av_count(inners) == 1)
    100          
668 15           return all_nums_sv;
669             }
670              
671             SV *ret;
672             struct Constraint *c;
673 6           alloc_constraint(&ret, &c, &constraint_All, 1);
674 6           sv_2mortal(ret);
675              
676 6 100         c->args[0] = SvREFCNT_inc(inners);
677              
678 6           return ret;
679             }
680              
681             #define MAKE_0ARG_CONSTRAINT(name) S_make_0arg_constraint(aTHX_ #name, &constraint_##name)
682 98           static void S_make_0arg_constraint(pTHX_ const char *name, ConstraintFunc *func)
683             {
684 98           HV *stash = gv_stashpvs("Data::Checks", GV_ADD);
685 98           AV *exportok = get_av("Data::Checks::EXPORT_OK", GV_ADD);
686              
687 98           SV *namesv = newSVpvf("Data::Checks::%s", name);
688              
689             /* Before perl 5.38, XSUBs cannot be exported lexically. newCONSTSUB() makes
690             * XSUBs. We'll have to build our own constant-value sub instead
691             */
692              
693 98           I32 floor_ix = start_subparse(FALSE, 0);
694              
695             SV *sv;
696             struct Constraint *constraint;
697 98           alloc_constraint(&sv, &constraint, func, 0);
698              
699 98           OP *body = make_argcheck_ops(0, 0, 0, namesv);
700 98           body = op_append_elem(OP_LINESEQ,
701             body,
702             newSTATEOP(0, NULL,
703             newSVOP(OP_CONST, 0, sv)));
704              
705 98           CV *cv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, namesv), NULL, NULL, body);
706 98           cv_set_call_checker(cv, &ckcall_constfold, &PL_sv_undef);
707              
708 98           av_push(exportok, newSVpv(name, 0));
709 98           }
710              
711             static XOP xop_make_constraint;
712 99           static OP *pp_make_constraint(pTHX)
713             {
714 99           dSP;
715 99           int nargs = PL_op->op_private;
716              
717             SV *ret;
718 99           switch(nargs) {
719 45           case 1:
720             {
721 45           SV *(*mk_constraint)(pTHX_ SV *arg0) =
722 45           (SV * (*)(pTHX_ SV *))cUNOP_AUX->op_aux;
723              
724 45           SV *arg0 = POPs;
725              
726 45           ret = (*mk_constraint)(aTHX_ arg0);
727 45           break;
728             }
729              
730 7           case 2:
731             {
732 7           SV *(*mk_constraint)(pTHX_ SV *arg0, SV *arg1) =
733 7           (SV * (*)(pTHX_ SV *, SV *))cUNOP_AUX->op_aux;
734              
735 7           SV *arg1 = POPs;
736 7           SV *arg0 = POPs;
737              
738 7           ret = (*mk_constraint)(aTHX_ arg0, arg1);
739 7           break;
740             }
741              
742 47           case (U8)-1:
743             {
744 47           SV *(*mk_constraint)(pTHX_ size_t nargs, SV **args) =
745 47           (SV * (*)(pTHX_ size_t, SV **))cUNOP_AUX->op_aux;
746              
747 47 100         SV **svp = PL_stack_base + POPMARK + 1;
748 47           size_t nargs = SP - svp + 1;
749 47           SP -= nargs;
750              
751 47 100         if(!nargs)
752 2 50         EXTEND(SP, 1);
753              
754 47           ret = (*mk_constraint)(aTHX_ nargs, svp);
755 47           break;
756             }
757              
758 0           default:
759 0           croak("ARGH unreachable nargs=%d", nargs);
760             }
761              
762 99           PUSHs(ret);
763              
764 99           RETURN;
765             }
766              
767             #define MAKE_1ARG_CONSTRAINT(name) S_make_1arg_constraint(aTHX_ #name, &mk_constraint_##name)
768 98           static void S_make_1arg_constraint(pTHX_ const char *name, SV *(*mk_constraint)(pTHX_ SV *arg0))
769             {
770 98           HV *stash = gv_stashpvs("Data::Checks", GV_ADD);
771 98           HV *constraint_stash = gv_stashpvs("Data::Checks::Constraint", GV_ADD);
772 98           AV *exportok = get_av("Data::Checks::EXPORT_OK", GV_ADD);
773              
774 98           SV *namesv = newSVpvf("Data::Checks::%s", name);
775              
776 98           I32 floor_ix = start_subparse(FALSE, 0);
777              
778 98           OP *mkop = newUNOP_AUX_CUSTOM(&pp_make_constraint, 0,
779             newSLUGOP(0),
780             (UNOP_AUX_item *)mk_constraint);
781 98           mkop->op_private = 1;
782              
783 98           OP *body = make_argcheck_ops(1, 0, 0, namesv);
784 98           body = op_append_elem(OP_LINESEQ,
785             body,
786             newSTATEOP(0, NULL, mkop));
787              
788 98           CV *cv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, namesv), NULL, NULL, body);
789 98           cv_set_call_checker(cv, &ckcall_constfold, &PL_sv_undef);
790              
791 98           av_push(exportok, newSVpv(name, 0));
792 98           }
793              
794             #define MAKE_2ARG_CONSTRAINT(name) S_make_2arg_constraint(aTHX_ #name, &mk_constraint_##name)
795 14           static void S_make_2arg_constraint(pTHX_ const char *name, SV *(*mk_constraint)(pTHX_ SV *arg0, SV *arg1))
796             {
797 14           HV *stash = gv_stashpvs("Data::Checks", GV_ADD);
798 14           HV *constraint_stash = gv_stashpvs("Data::Checks::Constraint", GV_ADD);
799 14           AV *exportok = get_av("Data::Checks::EXPORT_OK", GV_ADD);
800              
801 14           SV *namesv = newSVpvf("Data::Checks::%s", name);
802              
803 14           I32 floor_ix = start_subparse(FALSE, 0);
804              
805 14           OP *mkop = newUNOP_AUX_CUSTOM(&pp_make_constraint, 0,
806             newLISTOPn(OP_LIST, OPf_WANT_LIST, newSLUGOP(0), newSLUGOP(1), NULL),
807             (UNOP_AUX_item *)mk_constraint);
808 14           mkop->op_private = 2;
809              
810 14           OP *body = make_argcheck_ops(2, 0, 0, namesv);
811 14           body = op_append_elem(OP_LINESEQ,
812             body,
813             newSTATEOP(0, NULL, mkop));
814              
815 14           CV *cv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, namesv), NULL, NULL, body);
816 14           cv_set_call_checker(cv, &ckcall_constfold, &PL_sv_undef);
817              
818 14           av_push(exportok, newSVpv(name, 0));
819 14           }
820              
821             #define MAKE_nARG_CONSTRAINT(name) S_make_narg_constraint(aTHX_ #name, &mk_constraint_##name)
822 70           static void S_make_narg_constraint(pTHX_ const char *name, SV *(*mk_constraint)(pTHX_ size_t nargs, SV **args))
823             {
824 70           HV *stash = gv_stashpvs("Data::Checks", GV_ADD);
825 70           HV *constraint_stash = gv_stashpvs("Data::Checks::Constraint", GV_ADD);
826 70           AV *exportok = get_av("Data::Checks::EXPORT_OK", GV_ADD);
827              
828 70           SV *namesv = newSVpvf("Data::Checks::%s", name);
829              
830 70           I32 floor_ix = start_subparse(FALSE, 0);
831              
832 70           OP *mkop = newUNOP_AUX_CUSTOM(&pp_make_constraint, 0,
833             op_force_list(newLISTOPn(OP_LIST, OPf_WANT_LIST,
834             newUNOP(OP_RV2AV, OPf_WANT_LIST, newGVOP(OP_GV, 0, PL_defgv)),
835             NULL)),
836             (UNOP_AUX_item *)mk_constraint);
837 70           mkop->op_private = -1;
838              
839 70           OP *body = make_argcheck_ops(0, 0, '@', namesv);
840 70           body = op_append_elem(OP_LINESEQ,
841             body,
842             newSTATEOP(0, NULL, mkop));
843              
844 70           CV *cv = newATTRSUB(floor_ix, newSVOP(OP_CONST, 0, namesv), NULL, NULL, body);
845 70           cv_set_call_checker(cv, &ckcall_constfold, &PL_sv_undef);
846              
847 70           av_push(exportok, newSVpv(name, 0));
848 70           }
849              
850             /* This does NOT use SVf_quoted as that is intended for C's quoting
851             * rules; we want qq()-style perlish ones. This means that $ and @ need to be
852             * escaped as well.
853             */
854             #define sv_catsv_quoted(buf, sv, quote) S_sv_catsv_quoted(aTHX_ buf, sv, quote)
855 21           static void S_sv_catsv_quoted(pTHX_ SV *buf, SV *sv, char quote)
856             {
857             STRLEN len;
858 21           const char *s = SvPV_const(sv, len);
859 21           sv_catpvn(buf, "e, 1);
860 131 100         for(STRLEN i = 0; i < len; i++) {
861 110 50         if(len == 256) {
862 0           sv_catpvs(buf, "...");
863 0           break;
864             }
865 110           char c = s[i];
866 110 50         if(c == '\\' || c == quote || (quote != '\'' && (c == '$' || c == '@')))
    100          
    50          
    100          
867 3           sv_catpvs(buf, "\\");
868             /* TODO: UTF-8 */
869 110           sv_catpvn(buf, &c, 1);
870             }
871 21           sv_catpvn(buf, "e, 1);
872 21           }
873              
874             #define sv_catsv_quoted_list(buf, av, quote, sep) S_sv_catsv_quoted_list(aTHX_ buf, av, quote, sep)
875 4           static void S_sv_catsv_quoted_list(pTHX_ SV *buf, AV *av, char quote, char sep)
876             {
877 4           U32 n = av_count(av);
878 4           SV **vals = AvARRAY(av);
879 14 100         for(U32 i = 0; i < n; i++) {
880 10 100         if(i > 0)
881 6           sv_catpvn(buf, &sep, 1), sv_catpvs(buf, " ");
882 10           sv_catsv_quoted(buf, vals[i], quote);
883             }
884 4           }
885              
886 96           SV *DataChecks_stringify_constraint(pTHX_ struct Constraint *c)
887             {
888             const char *name = NULL;
889 96           SV *args = sv_2mortal(newSVpvn("", 0));
890              
891             /* such a shame C doesn't let us use function addresses as case labels */
892              
893             // 0arg
894 96 100         if (c->func == &constraint_Defined)
895             name = "Defined";
896 91 100         else if(c->func == &constraint_Object)
897             name = "Object";
898 85 100         else if(c->func == &constraint_ArrayRef)
899             name = "ArrayRef";
900 83 100         else if(c->func == &constraint_HashRef)
901             name = "HashRef";
902 81 100         else if(c->func == &constraint_Callable)
903             name = "Callable";
904 79 100         else if(c->func == &constraint_Num)
905             name = "Num";
906 77 100         else if(c->func == &constraint_Str)
907             name = "Str";
908             // 1arg
909 58 100         else if(c->func == &constraint_Isa) {
910             name = "Isa";
911 3           sv_catsv_quoted(args, c->args[0], '"');
912             }
913 55 100         else if(c->func == &constraint_StrMatch) {
914             name = "StrMatch";
915 2           sv_catpvs(args, "qr");
916 2           sv_catsv_quoted(args, c->args[0], '/');
917             }
918 53 100         else if(c->func == &constraint_Maybe) {
919             name = "Maybe";
920 2           args = stringify_constraint_sv(c->args[0]);
921             }
922             // 2arg
923 51 100         else if(c->func == &constraint_NumBound) {
924 26 100         if(!c->args[0])
925 7 100         name = (c->flags & NUMBOUND_UPPER_INCLUSIVE ) ? "NumLE" : "NumLT";
926 19 100         else if(!c->args[1])
927 7 100         name = (c->flags & NUMBOUND_LOWER_INCLUSIVE ) ? "NumGE" : "NumGT";
928 12 100         else if(c->flags == NUMBOUND_LOWER_INCLUSIVE)
929             name = "NumRange";
930             else {
931             /* This was optimised from an All() call on at least two different ones;
932             * we'll have to just stringify it as best we can
933             */
934             name = "All";
935 11 100         sv_catpvf(args, "NumG%c(%" SVf "), NumL%c(%" SVf ")",
    100          
936             (c->flags & NUMBOUND_LOWER_INCLUSIVE) ? 'E' : 'T', SVfARG(c->args[0]),
937             (c->flags & NUMBOUND_UPPER_INCLUSIVE) ? 'E' : 'T', SVfARG(c->args[1]));
938             }
939              
940 26 100         if(!SvCUR(args)) {
941 20 100         if(c->args[0])
942 13           sv_catsv(args, c->args[0]);
943 20 100         if(c->args[0] && c->args[1])
    100          
944 6           sv_catpvs(args, ", ");
945 20 100         if(c->args[1])
946 13           sv_catsv(args, c->args[1]);
947             }
948             }
949             // narg
950 25 100         else if(c->func == &constraint_NumEq) {
951             name = "NumEq";
952 5 100         if(SvTYPE(c->args[0]) != SVt_PVAV)
953 3           sv_catsv(args, c->args[0]);
954             else {
955 2           U32 n = av_count((AV *)c->args[0]);
956 2           SV **vals = AvARRAY(c->args[0]);
957 7 100         for(U32 i = 0; i < n; i++) {
958 5 100         if(i > 0)
959 3           sv_catpvs(args, ", ");
960 5           sv_catsv(args, vals[i]);
961             }
962             }
963             }
964 20 100         else if(c->func == &constraint_StrEq) {
965             name = "StrEq";
966 7 100         if(SvTYPE(c->args[0]) == SVt_PVAV)
967 2           sv_catsv_quoted_list(args, (AV *)c->args[0], '"', ',');
968             else
969 5           sv_catsv_quoted(args, c->args[0], '"');
970             }
971 13 100         else if(c->func == &constraint_Can) {
972             name = "Can";
973 3 100         if(SvTYPE(c->args[0]) == SVt_PVAV)
974 2           sv_catsv_quoted_list(args, (AV *)c->args[0], '"', ',');
975             else
976 1           sv_catsv_quoted(args, c->args[0], '"');
977             }
978 10 100         else if(c->func == &constraint_Any || c->func == &constraint_All) {
    50          
979 10 100         name = (c->func == &constraint_Any) ? "Any" : "All";
980 10 100         if(c->args[0]) {
981 8           U32 n = av_count((AV *)c->args[0]);
982 8           SV **inners = AvARRAY(c->args[0]);
983 27 100         for(U32 i = 0; i < n; i++) {
984 19 100         if(i > 0)
985 11           sv_catpvs(args, ", ");
986 19           sv_catsv(args, stringify_constraint_sv(inners[i]));
987             }
988             }
989             }
990              
991             else
992 0           return newSVpvs_flags("TODO: debug inspect constraint", SVs_TEMP);
993              
994 96           SV *ret = newSVpvf("%s", name);
995 96 100         if(SvCUR(args))
996 56           sv_catpvf(ret, "(%" SVf ")", SVfARG(args));
997              
998 96           return sv_2mortal(ret);
999             }
1000              
1001 14           void boot_Data_Checks__constraints(pTHX)
1002             {
1003 14           MAKE_0ARG_CONSTRAINT(Defined);
1004 14           MAKE_0ARG_CONSTRAINT(Object);
1005 14           MAKE_0ARG_CONSTRAINT(Str);
1006 14           MAKE_0ARG_CONSTRAINT(Num);
1007              
1008 14           MAKE_nARG_CONSTRAINT(StrEq);
1009 14           MAKE_1ARG_CONSTRAINT(StrMatch);
1010              
1011 14           MAKE_1ARG_CONSTRAINT(NumGT);
1012 14           MAKE_1ARG_CONSTRAINT(NumGE);
1013 14           MAKE_1ARG_CONSTRAINT(NumLE);
1014 14           MAKE_1ARG_CONSTRAINT(NumLT);
1015 14           MAKE_2ARG_CONSTRAINT(NumRange);
1016 14           MAKE_nARG_CONSTRAINT(NumEq);
1017              
1018 14           MAKE_1ARG_CONSTRAINT(Isa);
1019 14           MAKE_nARG_CONSTRAINT(Can);
1020 14           MAKE_0ARG_CONSTRAINT(ArrayRef);
1021 14           MAKE_0ARG_CONSTRAINT(HashRef);
1022 14           MAKE_0ARG_CONSTRAINT(Callable);
1023 14           MAKE_1ARG_CONSTRAINT(Maybe);
1024 14           MAKE_nARG_CONSTRAINT(Any);
1025 14           MAKE_nARG_CONSTRAINT(All);
1026              
1027 14           XopENTRY_set(&xop_make_constraint, xop_name, "make_constraint");
1028 14           XopENTRY_set(&xop_make_constraint, xop_desc, "make constraint");
1029 14           XopENTRY_set(&xop_make_constraint, xop_class, OA_UNOP_AUX);
1030 14           Perl_custom_op_register(aTHX_ &pp_make_constraint, &xop_make_constraint);
1031 14           }