File Coverage

blib/lib/HOI/Match.pm
Criterion Covered Total %
statement 71 72 98.6
branch 25 28 89.2
condition 8 12 66.6
subroutine 12 12 100.0
pod 1 4 25.0
total 117 128 91.4


line stmt bran cond sub pod time code
1             package HOI::Match;
2              
3             #use Alias qw(attr);
4              
5             require Exporter;
6              
7 2     2   35182 use Parse::Lex;
  2         32545  
  2         54  
8 2     2   765 use HOI::typeparser;
  2         13  
  2         207  
9              
10             our @ISA = qw( Exporter );
11             our @EXPORT_OK = qw( pmatch );
12             our $VERSION = '0.07';
13              
14             # The inline code below comes from Alias (https://metacpan.org/pod/Alias) with a patch from issue #64987 (https://rt.cpan.org/Public/Bug/Display.html?id=64987)
15              
16 2     2   1161 use Inline C => <<'END_C';
  2         34284  
  2         15  
17              
18             #ifndef PERL_VERSION
19             #include "patchlevel.h"
20             #define PERL_REVISION 5
21             #define PERL_VERSION PATCHLEVEL
22             #define PERL_SUBVERSION SUBVERSION
23             #endif
24              
25             #ifndef GvCV_set
26             #define GvCV_set(gv,cv) GvCV((gv)) = (cv)
27             #endif
28              
29             #if PERL_REVISION == 5 && (PERL_VERSION < 4 || (PERL_VERSION == 4 && PERL_SUBVERSION <= 75 ))
30             #define PL_stack_sp stack_sp
31             #endif
32              
33             void process_flag(char *varname, SV **svp, char **strp, STRLEN *lenp)
34             {
35             GV *vargv = gv_fetchpv(varname, FALSE, SVt_PV);
36             SV *sv = Nullsv;
37             char *str = Nullch;
38             STRLEN len = 0;
39              
40             if (vargv && (sv = GvSV(vargv))) {
41             if (SvROK(sv)) {
42             if (SvTYPE(SvRV(sv)) != SVt_PVCV)
43             croak("$%s not a subroutine reference", varname);
44             }
45             else if (SvOK(sv))
46             str = SvPV(sv, len);
47             }
48             *svp = sv;
49             *strp = str;
50             *lenp = len;
51             }
52              
53             void attr(SV *hashref)
54             {
55             dSP;
56             HV *hv;
57             int in_destroy = 0;
58             int deref_call;
59            
60             if (SvREFCNT(hashref) == 0)
61             in_destroy = 1;
62            
63             ++SvREFCNT(hashref); /* in case LEAVE wants to clobber us */
64              
65             if (SvROK(hashref) &&
66             (hv = (HV *)SvRV(hashref)) && (SvTYPE(hv) == SVt_PVHV))
67             {
68             SV *val, *tmpsv;
69             char *key;
70             I32 klen;
71             SV *keypfx, *attrpfx, *deref;
72             char *keypfx_c, *attrpfx_c, *deref_c;
73             STRLEN keypfx_l, attrpfx_l, deref_l;
74              
75             //process_flag("Alias::KeyFilter", &keypfx, &keypfx_c, &keypfx_l);
76             (keypfx = NULL), (keypfx_c = NULL), (keypfx_l = 0);
77             process_flag("AttrPrefix", &attrpfx, &attrpfx_c, &attrpfx_l);
78             //process_flag("Alias::Deref", &deref, &deref_c, &deref_l);
79             (deref = NULL), (deref_c = NULL),(deref_l = 0);
80             deref_call = (deref && !deref_c);
81            
82             LEAVE; /* operate at a higher level */
83            
84             (void)hv_iterinit(hv);
85             while ((val = hv_iternextsv(hv, &key, &klen))) {
86             GV *gv;
87             CV *cv;
88             int stype = SvTYPE(val);
89             int deref_this = 1;
90             int deref_objects = 0;
91              
92             /* check the key for validity by either looking at
93             * its prefix, or by calling &$Alias::KeyFilter */
94             if (keypfx) {
95             if (keypfx_c) {
96             if (keypfx_l && klen > keypfx_l
97             && strncmp(key, keypfx_c, keypfx_l))
98             continue;
99             } else {
100             //dSP;
101             SV *ret = Nullsv;
102             I32 i;
103            
104             ENTER; SAVETMPS; PUSHMARK(sp);
105             XPUSHs(sv_2mortal(newSVpv(key,klen)));
106             PUTBACK;
107             if (perl_call_sv(keypfx, G_SCALAR))
108             ret = *PL_stack_sp--;
109             SPAGAIN;
110             i = SvTRUE(ret);
111             FREETMPS; LEAVE;
112             if (!i)
113             continue;
114             }
115             }
116              
117             if (SvROK(val) && deref) {
118             if (deref_c) {
119             if (deref_l && !(deref_l == 1 && *deref_c == '0'))
120             deref_objects = 1;
121             }
122             else {
123             //dSP;
124             SV *ret = Nullsv;
125            
126             ENTER; SAVETMPS; PUSHMARK(sp);
127             XPUSHs(sv_2mortal(newSVpv(key,klen)));
128             XPUSHs(sv_2mortal(newSVsv(val)));
129             PUTBACK;
130             if (perl_call_sv(deref, G_SCALAR))
131             ret = *PL_stack_sp--;
132             SPAGAIN;
133             deref_this = SvTRUE(ret);
134             FREETMPS; LEAVE;
135             }
136             }
137            
138             /* attributes may need to be prefixed/renamed */
139             if (attrpfx) {
140             STRLEN len;
141             if (attrpfx_c) {
142             if (attrpfx_l) {
143             SV *keysv = sv_2mortal(newSVpv(attrpfx_c, attrpfx_l));
144             sv_catpvn(keysv, key, klen);
145             key = SvPV(keysv, len);
146             klen = len;
147             }
148             }
149             else {
150             //dSP;
151             SV *ret = Nullsv;
152            
153             ENTER; PUSHMARK(sp);
154             XPUSHs(sv_2mortal(newSVpv(key,klen)));
155             PUTBACK;
156             if (perl_call_sv(attrpfx, G_SCALAR))
157             ret = *PL_stack_sp--;
158             SPAGAIN; LEAVE;
159             key = SvPV(ret, len);
160             klen = len;
161             }
162             }
163              
164             if (SvROK(val) && (tmpsv = SvRV(val))) {
165             if (deref_call) {
166             if (!deref_this)
167             goto no_deref;
168             }
169             else if (!deref_objects && SvOBJECT(tmpsv))
170             goto no_deref;
171              
172             stype = SvTYPE(tmpsv);
173             if (stype == SVt_PVGV)
174             val = tmpsv;
175              
176             }
177             else if (stype != SVt_PVGV) {
178             no_deref:
179             val = sv_2mortal(newRV(val));
180             }
181            
182             /* add symbol, forgoing "used once" warnings */
183             gv = gv_fetchpv(key, GV_ADDMULTI, SVt_PVGV);
184            
185             switch (stype) {
186             case SVt_PVAV:
187             save_ary(gv);
188             break;
189             case SVt_PVHV:
190             save_hash(gv);
191             break;
192             case SVt_PVGV:
193             save_gp(gv,TRUE); /* hide previous entry in symtab */
194             break;
195             case SVt_PVCV:
196             cv = GvCV(gv);
197             SAVESPTR(cv);
198             GvCV_set(gv,Null(CV*));
199             break;
200             default:
201             save_scalar(gv);
202             break;
203             }
204             sv_setsv((SV*)gv, val); /* alias the SV */
205             }
206             ENTER; /* in lieu of the LEAVE far beyond */
207             }
208             if (in_destroy)
209             --SvREFCNT(hashref); /* avoid calling DESTROY forever */
210             else
211             SvREFCNT_dec(hashref);
212            
213             XPUSHs(hashref); /* simply return what we got */
214             }
215              
216             END_C
217              
218             my @tokens = (
219             qw (
220             LPAREN [\(]
221             RPAREN [\)]
222             CONCAT ::
223             NIL nil
224             IDENT [A-Za-z_][A-Za-z0-9_]*
225             CONST (?:0(?:\.[0-9]+)?)|(?:[1-9][0-9]*(?:\.[0-9]+)?)|(?:\".+\")|(?:\'.+\')
226             ),
227             COMMA => q/,/
228             );
229              
230             my $lexer = Parse::Lex->new(@tokens);
231             $lexer->skip('\s+');
232             my $parser = HOI::typeparser->new();
233              
234             sub lexana {
235 90     90 0 3395 my $token = $lexer->next;
236 90 100       4084 if (not $lexer->eoi) {
237 66         283 return ($token->name, $token->text);
238             } else {
239 24         117 return ('', undef);
240             }
241             }
242              
243             my %compiled_patterns;
244              
245             sub pcompile {
246 24     24 0 87 $lexer->from(shift);
247 24         11721 $parser->YYParse(yylex => \&lexana)
248             }
249              
250             sub astmatch {
251 208     208 0 187 my ($ast, $args) = @_;
252 208 50       162 return (0, {}) if ($#{$ast} ne $#{$args});
  208         229  
  208         415  
253             my %switches = (
254             "const" =>
255             sub {
256 64     64   75 my ($sym, $val) = @_;
257 64 100 66     266 if( (substr($sym, 0, 1) eq '\'') or (substr($sym, 0, 1) eq '"') ) {
258 52         59 my $quote = substr($sym, 0, 1);
259 52 100       252 return ($sym eq $quote.$val.$quote) ? (1, {}) : (0, {});
260             } else {
261 12 100       36 return ($sym == $val) ? (1, {}) : (0, {});
262             }
263             },
264             "any" =>
265             sub {
266 116     116   123 my ($sym, $val) = @_;
267 116 100       317 (1, ((substr($sym, 0, 1) ne '_') ? { $sym => $val } : {}))
268             },
269             "list" =>
270             sub {
271 28     28   24 my ($l, $val) = @_;
272 28 100 100     23 if (($#{$l} >= 0) and ($#{$val} >= 0)) {
  28 100 66     71  
  24         64  
  8         22  
273 20         53 my ($s1, $r1) = astmatch([ $l->[0] ], [ $val->[0] ]);
274 20         38 my ($s2, $r2) = astmatch([ $l->[1] ], [ [ @$val[1..$#{$val}] ] ]);
  20         43  
275 20         79 return ($s1 * $s2, { %$r1, %$r2 });
276 4         10 } elsif (($#{$l} < 0) and ($#{$val} < 0)) {
277 4         7 return (1, {});
278             } else {
279 4         8 return (0, {});
280             }
281             },
282             "adt" =>
283             sub {
284 12     12   21 my ($adt, $val) = @_;
285 12 50 33     39 return (0, {}) if ((not defined $val->{"type"}) or (not defined $val->{"val"}));
286 12         17 my ($sym, $typelist) = ($adt->[0], $adt->[1]);
287 12 50       25 return (0, {}) if ($adt->[0] ne $val->{"type"});
288 12 100       8 return (0, {}) if ($#{$adt->[1]} != $#{$val->{"val"}});
  12         15  
  12         27  
289 10         26 astmatch($adt->[1], $val->{"val"})
290             }
291 208         1478 );
292 208         235 my $ret = {};
293 208         219 for (my $idx = 0; $idx <= $#{$ast}; $idx++) {
  362         609  
294 220         439 my ($status, $result) = $switches{$ast->[$idx]->{"kind"}}->($ast->[$idx]->{"val"}, $args->[$idx]);
295 220 100       274 if ($status) {
296 154         511 $ret = { %$ret, %$result };
297             } else {
298 66         708 return (0, {})
299             }
300             }
301 142         1418 (1, $ret)
302             }
303              
304             sub pmatch {
305 92     92 1 7213 my $patterns = \@_;
306             sub {
307 92     92   111 my $args = \@_;
308 92         162 while (@$patterns) {
309 158         176 my $pattern = shift @$patterns;
310 158         158 my $handler = shift @$patterns;
311 158         483 my $pattern_sig = (caller(1))[3].$pattern;
312 158 100       398 $compiled_patterns{$pattern_sig} = pcompile($pattern) if (not defined $compiled_patterns{$pattern_sig});
313 158         1742 my $pattern_ast = $compiled_patterns{$pattern_sig};
314 158         221 my ($status, $results) = astmatch($pattern_ast, $args);
315 158 100       325 if ($status) {
316 92         274 my ($package) = caller(1);
317 92         142 local $AttrPrefix = $package.'::';
318 92         96 my $attr_prefix = $package.'::';
319 92         328 attr $results;
320 92         203 return $handler->(%$results);
321             }
322             }
323             0
324 0           }
325 92         323 }
326              
327             1;
328             __END__