File Coverage

blib/lib/Parser/Combinators.pm
Criterion Covered Total %
statement 278 456 60.9
branch 116 236 49.1
condition 2 3 66.6
subroutine 38 61 62.3
pod 0 34 0.0
total 434 790 54.9


line stmt bran cond sub pod time code
1             package Parser::Combinators;
2              
3 2     2   41537 use strict;
  2         4  
  2         58  
4 2     2   55 use 5.010;
  2         7  
5             our $VERSION = '0.04';
6              
7 2     2   11 use Exporter 'import';
  2         15  
  2         142  
8              
9             @Parser::Combinators::EXPORT = qw(
10             sequence
11             choice
12             try
13             maybe
14             regex
15             parens
16             char
17             sepBy
18             oneOf
19             word
20             natural
21             symbol
22             greedyUpto
23             upto
24             many
25             many1
26             whiteSpace
27             comma
28             matches
29             unwrap
30             empty
31             getParseTree
32             bindP
33             returnP
34             $V
35             );
36              
37 2     2   3662 use Data::Dumper;
  2         22769  
  2         12294  
38             $Data::Dumper::Indent=0;
39             $Data::Dumper::Terse =1;
40             our $V= 0;
41              
42             # Forward declarations with prototypes
43              
44             #sub symbol ($);
45             #sub char($);
46             #sub maybe($);
47             #sub sepBy($$);
48             #sub oneOf($);
49              
50              
51             # I want to write the parser using lists, because sequencing is the most common operation.
52             # So I need a function to generate the actual parser from the lists
53             # which is actually a sequence of parsers
54             # The first arg is a list ref, the second arg is an optional code ref to process the returned list of matches
55             sub sequence_ORIG {
56 0     0 0 0 (my $plst, my $proc)=@_;
57 0     0   0 my $gen = sub { (my $str)=@_;
58 0 0       0 say "* sequence( '$str' )" if $V;
59 0         0 my $matches=[];
60 0         0 my $st=1;
61 0         0 my $str2='';
62 0         0 my $ms=undef; # Why not []?
63 0         0 for my $p (@{$plst}) {
  0         0  
64 0 0       0 if (ref($p) eq 'CODE') {
    0          
65 0         0 ($st, $str, $ms)=$p->($str);
66             } elsif (ref($p) eq 'HASH') {
67 0         0 my %hp=%{$p};
  0         0  
68 0         0 (my $k, my $pp) = each %hp;
69 0         0 ($st, $str, my $mms)=$pp->($str);
70 0         0 $ms = {$k => $mms};
71             } else { # assuming it's ARRAY
72 0         0 my $p2 = sequence($p);
73 0         0 ($st, $str, $ms)=$p2->($str)
74             }
75 0 0       0 if (!$st) {
76 0         0 return (0,$str,[]);
77             }
78 0         0 push @{$matches},$ms;# if scalar @{ $ms };
  0         0  
79             }
80 0 0       0 if (defined($proc)) {
81 0 0       0 if( ref($proc) eq 'CODE') {
82 0         0 return (1,$str,$proc->($matches));
83             } else {
84 0 0       0 say 'TROUBLE: <',Dumper($plst),'><',Dumper($proc),'>' if $V;
85 0         0 return (1,$str,$matches);
86             }
87             } else {
88 0         0 return (1,$str,$matches)
89             }
90 0         0 };
91 0         0 return $gen;
92             }
93              
94             sub foldl {
95 19     19 0 37 (my $f, my $acc, my $ls)=@_;
96 19         29 for my $elt (@{$ls}) {
  19         57  
97 51         130 $acc = $f->($acc,$elt);
98             }
99 19         57 return $acc;
100             }
101              
102             sub sequence {
103 11     11 0 31 (my $plst, my $proc)=@_;
104 19     19   4302 my $gen = sub { (my $str)=@_;
105 19 50       2706 say "* sequence( '$str' )" if $V;
106 51         78 my $f = sub { (my $acc, my $p) = @_;
107 51         85 (my $st1, my $str1, my $matches) = @{ $acc };
  51         107  
108 51         136 (my $st2, my $str2, my $ms) = apply($p,$str1);
109             # do {
110             # if (ref($p) eq 'CODE') {
111             # $p->($str1);
112             # } elsif (ref($p) eq 'HASH') {
113             # my %hp=%{$p};
114             # (my $k, my $pp) = each %hp;
115             # (my $st, my $str, my $mms)=$pp->($str1);
116             # my $ms = {$k => $mms};
117             # ($st, $str, $ms)
118             # } else { # assuming it's ARRAY
119             # die Dumper($p);
120             # my $p2 = sequence($p);
121             # $p2->($str1)
122             # }
123             # };
124 51 100       166 if ($st2*$st1==0) {
125 8         39 return [0,$str1,[]];
126             } else {
127 43         74 return [1,$str2,[ @{$matches},$ms]];
  43         255  
128             }
129 19         224 };
130 19         42 (my $status, my $str, my $matches) = @{ foldl($f, [1,$str,[]],$plst) };
  19         100  
131 19 100       76 if ($status == 0) {
    50          
132 4         27 return (0,$str,[]);
133             } elsif (defined($proc)) {
134 0 0       0 if( ref($proc) eq 'CODE') {
135 0         0 return (1,$str,$proc->($matches));
136             } else {
137 0 0       0 say 'TROUBLE: <',Dumper($plst),'><',Dumper($proc),'>' if $V;
138 0         0 return (1,$str,$matches);
139             }
140             } else {
141 15         126 return (1,$str,$matches)
142             }
143 11         67 };
144 11         50 return $gen;
145             }
146              
147             sub sequence_noproc {
148 0     0 0 0 (my $plst )=@_;
149 0     0   0 my $gen = sub { (my $str)=@_;
150 0 0       0 say "* sequence( '$str' )" if $V;
151 0         0 my $f = sub { (my $acc, my $p) = @_;
152 0         0 (my $st1, my $str1, my $matches) = @{ $acc };
  0         0  
153 0         0 (my $st2, my $str2, my $ms) = do {
154 0 0       0 if (ref($p) eq 'CODE') {
    0          
155 0         0 $p->($str1);
156             } elsif (ref($p) eq 'HASH') {
157 0         0 my %hp=%{$p};
  0         0  
158 0         0 (my $k, my $pp) = each %hp;
159 0         0 (my $st, my $str, my $mms)=$pp->($str1);
160 0         0 my $ms = {$k => $mms};
161 0         0 ($st, $str, $ms)
162             } else { # assuming it's ARRAY
163 0         0 my $p2 = sequence($p);
164 0         0 $p2->($str1)
165             }
166             };
167 0 0       0 if ($st2*$st1==0) {
168 0         0 return [0,$str1,[]];
169             } else {
170 0         0 return [1,$str2,[ @{$matches},$ms]];
  0         0  
171             }
172 0         0 };
173 0         0 (my $status, my $str, my $matches) = @{ foldl($f, [1,$str,[]],$plst) };
  0         0  
174 0 0       0 if ($status == 0) {
175 0         0 return (0,$str,[]);
176             } else {
177 0         0 return (1,$str,$matches)
178             }
179 0         0 };
180 0         0 return $gen;
181             }
182             # In the best tradition, bind() and return()
183             sub bindP {
184 0     0 0 0 (my $p1,my $p2) =@_;
185 0     0   0 my $gen = sub {(my $str1) =@_;
186 0 0       0 say "* bindP( \'$str1\' )" if $V;
187 0         0 my $matches=undef;
188 0         0 (my $st1,my $str2,my $m1) = $p1->( $str1 );
189 0         0 push @{$matches},$m1;
  0         0  
190 0 0       0 if ($st1) {
191 0         0 say "bind: p1( $str1 ) matched,[$m1] try p2( $str2 )";
192 0         0 (my $st2,my $str3, my $m2) = $p2->( $str2 );
193 0 0       0 if(ref($m2) eq 'ARRAY' ){
    0          
194 0         0 $matches =[ @{$matches},@{$m2} ];
  0         0  
  0         0  
195             } elsif (defined $m2) {
196 0         0 push @{$matches},$m2;
  0         0  
197             }
198 0         0 return ($st2,$str3,$matches);
199             } else {
200 0         0 return (0,$str1,undef);
201             }
202 0         0 };
203 0         0 return $gen;
204             }
205              
206             # Only we can't call it 'return' so let's call it enter :-)
207             sub returnP {
208             sub {
209 0     0   0 (my $str) = @_;
210 0         0 return (0,$str,undef);
211             }
212 0     0 0 0 }
213              
214             # Choice: try every parser in the list until one succeeds or return fail. '<|>' in Parsec
215             # FIXME: Prototype does not guarantee that parens can be omitted. Should make it binary for that.
216             sub choice ($$;@) {
217 1     1 0 3 my @parsers=@_;
218 2     2   4 my $gen = sub { (my $str)= @_;
219 2 50       201 say "* choice('$str')" if $V;
220 2         7 for my $p (@parsers) {
221 3         55 my $status=0;
222 3         6 my $matches=[];
223 3         17 (my $status, $str, my $matches) = apply($p,$str);
224              
225             # if (ref($p) eq 'CODE') {
226             # ($status, $str, $matches)=$p->($str);
227             # } elsif (ref($p) eq 'ARRAY') {
228             # ($status, $str, $matches)=sequence($p)->($str);
229             # } elsif (ref($p) eq 'HASH') {
230             # my %hp = %{$p};
231             # (my $k, my $pp) = each %hp;
232             # ($status, $str, my $mms)=$pp->($str);
233             # $matches = {$k => $mms};
234             # } else {
235             # die Dumper($p);
236             # }
237 3 100       9 if ($status) {
238 2 50       172 say "choice: remainder => <$str>" if $V;
239 2 50       20 say "choice: matches => [".Dumper($matches)."]" if $V;
240 2         393 return ($status, $str, $matches);
241             }
242             }
243 0         0 return (0, $str, []);
244 1         6 };
245 1         5 return $gen;
246             }
247             # Normally, when a parser parses a string, it removes the portion that matched. If you want to keep the string, wrap the parser in try()
248             sub try {
249 0     0 0 0 (my $p)=@_;
250             my $gen = sub {
251 0     0   0 (my $str)=@_;
252 0 0       0 say "* try( '$str' )" if $V;
253 0         0 (my $status, my $rest, my $matches)=$p->($str);
254 0 0       0 if ($status) {
255 0 0       0 say "try: remainder => <$rest>" if $V;
256 0 0       0 say "try: matches => [".Dumper($matches)."]" if $V;
257 0         0 return (1, $rest, $matches);
258             } else {
259 0 0       0 say "try: match failed => <$str>" if $V;
260 0         0 return (0, $str, $matches);
261             }
262 0         0 };
263 0         0 return $gen;
264             }
265              
266             # maybe() is like try() but always succeeds
267             # it returns the matches and the consumed string or the orig string and no matches
268 3     3 0 4 sub maybe { (my $p)=@_;
269 9     9   18 my $gen = sub { (my $str)=@_;
270 9 50       1045 say "* maybe('$str')" if $V;
271             # (my $status, my $rest, my $matches)= do {
272             #
273             # if (ref($p) eq 'CODE') {
274             # $p->($str);
275             # } elsif (ref($p) eq 'ARRAY') {
276             # sequence($p)->($str);
277             # } elsif (ref($p) eq 'HASH') {
278             # my %hp = %{$p};
279             # (my $k, my $pp) = each %hp;
280             # (my $status, my $str2, my $mms)=$pp->($str);
281             # my $matches = {$k => $mms};
282             # ($status, $str2, $matches);
283             # } else {
284             # die Dumper($p);
285             # }
286             # };
287            
288 9         33 (my $status, my $rest, my $matches)=apply($p,$str);
289 9 100       80 if ($status) {
290 6 50       26 say "maybe matches: [".Dumper($matches)."]" if $V;
291 6         1040 return (1, $rest, $matches);
292             } else {
293 3 50       285 say "maybe: no matches for <$str>" if $V;
294 3         19 return (1, $str, undef);
295             }
296 3         11 };
297 3         8 return $gen;
298             }
299              
300              
301 3     3 0 6 sub parens { (my $p)= @_;
302 9     9   20 my $gen = sub { (my $str)=@_;
303 9 50       1015 say "* parens($str)" if $V;
304 9         20 my $matches=undef;
305 9         35 (my $status, my $str3, my $ch)=char('(')->($str);
306 9 100       87 if ($status==1) {
307 6         11 my $str4 = $str3; $str4=~s/\s*//;
  6         33  
308 6         22 (my $st,my $str4s,$matches)=$p->($str4);
309 6 50       608 say "parens: remainder => <$str4s>" if $V;
310 6 50       37 say "parens: matches => [".Dumper($matches)."]" if $V;
311 6         1002 $status*=$st;
312 6 50       20 if ($status==1) {
313 6         18 (my $st, my $str5, my $ch)=char(')')->($str4s);
314             # if ($st==1) {
315             # $str=~s/\s*//;
316             # }
317 6         45 $status*=$st;
318 6 50       21 if ($status==1) { # OK!
319 6         11 my $str6 = $str5; $str6=~s/^\s*//;
  6         31  
320 6 50       643 say "parens: remainder => <$str5>" if $V;
321 6 50       35 say "parens: matches => ".Dumper($matches)."" if $V;
322 6         960 return (1,$str6, $matches);
323             } else { # parse failed on closing paren
324 0         0 return (0,$str5, $matches);
325             }
326             } else { # parse failed on $ref
327 0         0 return (0,$str4, $matches);
328             }
329             } else { # parse failed on opening paren
330 3         14 return (0,$str3,undef);
331             }
332 3         16 };
333 3         13 return $gen;
334             }
335              
336 29     29 0 612 sub char { (my $ch)=@_;
337 29     29   66 my $gen = sub { (my $str)=@_;
338 29 100       2739 say "* char('$ch', '$str')" if $V;
339 29 100       178 if (substr($str,0,1) eq $ch) {
340 19 100       1798 say "char: matched \'$ch\' " if $V;
341 19 100       1818 say "char: remainder <".substr($str,1).">" if $V;
342 19         122 return (1,substr($str,1),$ch);
343             } else {
344 10         78 return (0,$str,undef);
345             }
346 29         196 };
347 29         116 return $gen;
348             }
349              
350 2     2 0 3 sub sepBy ($$) { (my $sep, my $p)=@_;
351 6     6   13 my $gen = sub { (my $str)=@_;
352 6         14 my $matches=[];
353 6 50       526 say "* sepBy('$sep', '$str')" if $V;
354 6         24 (my $status,my $str1,my $m)=$p->($str);
355 6 50       25 if ($status) {
356 6         9 push @{$matches},$m;
  6         18  
357 6 50       582 say "sepBy: remainder => <$str1>" if $V;
358 6         32 ($status,my $str2,$m)=char($sep)->($str1);
359 6         56 while( $status ) {
360 6         15 my $str2s=$str2;$str2s=~s/^\s*//;
  6         73  
361 6         19 (my $st,my $str3,$m)=$p->($str2s);
362 6         16 push @{$matches},$m;
  6         17  
363 6         22 ($status,$str2,$m)=char($sep)->($str3);
364             }
365 6 50       46 say "sepBy matches => [".Dumper($matches)."]" if $V;
366 6         1259 return (1, $str2, $matches);
367             } else { # first match failed.
368 0         0 return (0,$str1,undef);
369             }
370 2         14 };
371 2         7 return $gen;
372             }
373             # This is a lexeme parser, so it skips trailing whitespace
374             # Should be called "identifier" I think
375             sub word {
376 13     13   33 my $gen = sub { (my $str)=@_;
377 13 100       784 say "* word( '$str' )" if $V;
378 13 100       67 if(
379             $str=~/^(\w+)/
380             ) {
381 12         37 my $m=$1;
382 12         19 my $matches=$m;
383 12         230 $str=~s/^$m\s*//;
384 12 100       811 say "word: remainder => <$str>" if $V;
385 12 100       1337 say "word: matches => [$matches]" if $V;
386 12         72 return (1,$str, $matches);
387             } else {
388 1 50       4 say "word: match failed => <$str>" if $V;
389 1         4 return (0,$str, undef); # assumes $status is 0|1, $str is string, $matches is [string]
390             }
391 6     6 0 838 };
392 6         25 return $gen;
393             }
394             sub identifier {
395 0     0 0 0 word();
396             }
397              
398             # matches an unsigned integer
399             sub natural {
400 6     6   18 my $gen = sub { (my $str)=@_;
401 6 100       273 say "* natural( '$str' )" if $V;
402 6         20 my $status=0;
403 6 100       23 if(
404             $str=~/^(\d+)/
405             ) {
406 4         11 my $m=$1;
407 4         7 my $matches=$m;
408 4         6 $status=1;
409 4         34 $str=~s/^$m\s*//;
410 4 100       178 say "natural: remainder => <$str>" if $V;
411 4 100       178 say "natural: matches => [$matches]" if $V;
412 4         17 return ($status,$str, $matches);
413             } else {
414 2 100       96 say "natural: match failed => <$str>" if $V;
415 2         8 return ($status,$str, undef); # assumes $status is 0|1, $str is string, $matches is [string]
416             }
417 5     5 0 811 };
418 5         24 return $gen;
419             }
420              
421             # As in Parsec, parses a literal and removes trailing whitespace
422 11     11 0 1607 sub symbol ($) { (my $lit_str) = @_;
423 11         36 $lit_str=~s/(\W)/\\$1/g;
424 17     17   45 my $gen = sub { (my $str)=@_;
425 17 100       1328 say "* symbol( '$lit_str', '$str' )" if $V;
426 17         41 my $status=0;
427 17 100       445 if(
428             $str=~/^\s*$lit_str\s*/
429             ) {
430 14         45 my $m=$1;
431 14         23 my $matches=$lit_str;
432 14         19 $status=1;
433 14         232 $str=~s/^\s*$lit_str\s*//;
434 14 100       889 say "symbol: remainder => <$str>" if $V;
435 14 100       821 say "symbol: matches => [$matches]" if $V;
436 14         73 return ($status,$str, $matches);
437             } else {
438 3 100       141 say "symbol: match failed => <$str>" if $V;
439 3         13 return ($status,$str, undef);
440             }
441 11         60 };
442 11         42 return $gen;
443             }
444              
445             # This parser parses anything up to the last occurence ofa given literal and trailing whitespace
446             sub greedyUpto {
447 1     1 0 289 (my $lit_str) = @_;
448 1         5 $lit_str=~s/(\W)/\\$1/g;
449             my $gen = sub {
450 1     1   5 (my $str)=@_;
451 1 50       4 say "* greedyUpto( \'$lit_str\', \'$str\' )" if $V;
452 1 50       21 if(
453             $str=~/^(.*)\s*$lit_str\s*/
454             ) {
455 1         2 my $m=$1;
456 1         7 $m=~s/\s*$//;
457 1 50       4 my $matches= $m eq '' ? undef : $m;
458 1         12 $str=~s/^.*$lit_str\s*//;
459 1 50       3 say "greedyUpto: remainder => <$str>" if $V;
460 1 50       10 say "greedyUpto: matches => [$matches]" if $V;
461 1         4 return (1,$str, $matches);
462             } else {
463 0 0       0 say "greedyUpto: match failed => <$str>" if $V;
464 0         0 return (0,$str, undef);
465             }
466 1         5 };
467 1         3 return $gen;
468             }
469              
470             # This parser parses anything up to the last occurence of a given literal and trailing whitespace
471             sub upto {
472 1     1 0 290 (my $lit_str )= @_;
473 1         7 $lit_str=~s/(\W)/\\$1/g;
474             my $gen = sub {
475 1     1   5 (my $str)=@_;
476 1 50       3 say "upto1 \'$lit_str\': <$str>" if $V;
477 1 50       19 if(
478             $str=~/^(.*?)\s*$lit_str\s*/
479             ) {
480 1         2 my $m=$1;
481 1 50       4 my $matches= $m eq '' ? undef : $m;
482 1         11 $str=~s/^.*?$lit_str\s*//;
483 1 50       3 say "upto: remainder => <$str>" if $V;
484 1 50       5 say "upto: matches => [$matches]" if $V;
485 1         4 return (1,$str, $matches);
486             } else {
487 0 0       0 say "upto: match failed => <$str>" if $V;
488 0         0 return (0,$str, undef);
489             }
490 1         5 };
491 1         4 return $gen;
492             }
493              
494              
495             # `many`, as in Parsec, parses 0 or more the specified parsers
496             sub many {
497 0     0 0 0 (my $parser) = @_;
498             my $gen = sub {
499 0     0   0 (my $str)=@_;
500 0 0       0 print "* many( '$str' )\n" if $V;
501 0         0 (my $status,$str,my $m)=$parser->($str);
502 0 0       0 if ($status) {
503 0         0 my $matches = [$m];
504 0         0 while( $status==1 ) {
505 0         0 ($status,$str,$m)=$parser->($str);
506 0         0 push @{$matches},$m;
  0         0  
507             }
508 0 0       0 print "many: remainder => <$str>\n" if $V;
509 0 0       0 print "many: matches => [".Dumper($matches)."]\n" if $V;
510 0         0 return (1, $str, $matches);
511             } else { # first match failed.
512 0 0       0 print "many: first match failed => <$str>\n" if $V;
513 0         0 return (1, $str,undef);
514             }
515 0         0 };
516 0         0 return $gen;
517             }
518              
519              
520             # `many1`, as in Parsec, parses 1 or more the specified parsers
521             sub many1 {
522 0     0 0 0 (my $parser) = @_;
523             my $gen = sub {
524 0     0   0 (my $str)=@_;
525 0         0 my $matches=[];
526 0 0       0 say "* many( '$str' )" if $V;
527 0         0 (my $status,$str,my $m)=$parser->($str);
528 0 0       0 if ($status) {
529 0         0 push @{$matches},$m;
  0         0  
530 0         0 while( $status==1 ) {
531 0         0 (my $status,$str,$m)=$parser->($str);
532 0         0 push @{$matches},$m;
  0         0  
533             }
534 0 0       0 say "many: remainder => <$str>" if $V;
535 0 0       0 say "many: matches => [".Dumper($matches)."]" if $V;
536             } else { # first match failed.
537 0 0       0 say "many: first match failed => <$str>" if $V;
538 0         0 return (0, $str,undef);
539             }
540 0         0 return (1, $str, $matches);
541 0         0 };
542 0         0 return $gen;
543             }
544              
545             sub comma {
546 7     7   17 my $gen = sub { (my $str) = @_;
547 7 100       1085 say "* comma( '$str' )" if $V;
548 7         70 my $st = ($str=~s/^\s*,\s*//);
549 7 100       26 if ($st) {
550 5 100       481 say "comma: match" if $V;
551             } else {
552 2 50       99 say "comma: match failed" if $V;
553             }
554 7         58 return ($st, $str, undef);
555 3     3 0 269 };
556 3         18 return $gen;
557             }
558              
559             sub semi {
560 0     0   0 my $gen = sub { (my $str) = @_;
561 0 0       0 say "* semi( '$str' )" if $V;
562 0         0 my $st = ($str=~s/^\s*;\s*//);
563 0         0 return ($st, $str, undef);
564 0     0 0 0 };
565 0         0 return $gen;
566             }
567              
568              
569              
570             # strip leading whitespace, always success
571             sub whiteSpace {
572 6     6   19 my $gen = sub { (my $str)=@_;
573 6 100       365 say "* whiteSpace( \'$str\' )" if $V;
574 6         31 $str=~s/^(\s*)//;
575 6         17 my $m=$1;
576 6         25 return (1,$str,$m)
577 4     4 0 816 };
578 4         15 return $gen;
579             }
580              
581 0     0 0 0 sub oneOf { (my $patt_lst) = @_;
582 0     0   0 my $gen = sub { (my $str)= @_;
583 0 0       0 say "* oneOf([".join('|',@{$patt_lst})."],'$str')" if $V;
  0         0  
584 0         0 for my $p (@{$patt_lst}) {
  0         0  
585 0         0 (my $status, $str, my $matches)= symbol($p)->($str);
586 0 0       0 if ($status) {
587 0 0       0 say "choice: remainder => <$str>" if $V;
588 0 0       0 say "choice: matches => [".Dumper($matches)."]" if $V;
589 0         0 return (1, $str, $matches);
590             }
591             }
592 0         0 return (0, $str, undef);
593 0         0 };
594 0         0 return $gen;
595             }
596              
597             # Enough rope: this parser will parse whatever the regex is, stripping trailing whitespace
598 1     1 0 2 sub regex { (my $regex_str) = @_;
599 6     6   13 my $gen = sub { (my $str)=@_;
600 6 50       683 say "* regex( '/$regex_str/', '$str' )" if $V;
601 6         14 my $matches=undef;
602 6 50       78 if(
603             $str=~s/($regex_str)\s*//
604             ) {
605 6         18 my $m=$1;
606 6         12 $matches=$m;
607 6 50       693 say "regex: remainder => <$str>" if $V;
608 6 50       668 say "regex: matches => [$matches]" if $V;
609 6         39 return (1,$str, $matches);
610             } else {
611 0 0       0 say "regex: match failed => <$str>" if $V;
612             }
613 0         0 return (0,$str, $matches); # assumes $status is 0|1, $str is string, $matches is [string]
614 1         4 };
615 1         5 return $gen;
616             }
617              
618 63     63 0 114 sub apply { (my $p, my $str) = @_;
619              
620 63 100       249 if (ref($p) eq 'CODE') {
    100          
    50          
621 39         185 return $p->($str);
622             } elsif (ref($p) eq 'ARRAY') {
623 6         28 return sequence($p)->($str);
624             } elsif (ref($p) eq 'HASH') {
625 18         25 my %hp = %{$p};
  18         74  
626 18         56 (my $k, my $pp) = each %hp;
627 18         49 (my $status, my $str2, my $mms)=$pp->($str);
628 18         57 my $matches = {$k => $mms};
629 18         86 return ($status, $str2, $matches);
630             } else {
631 0         0 die Dumper($p);
632             }
633             }
634              
635              
636             sub matches {
637 0     0 0 0 return @{$_[0]};
  0         0  
638             }
639              
640             sub unwrap {
641 0     0 0 0 (my $elt_in_array)=@_;
642 0         0 my $elt = shift @{$elt_in_array};
  0         0  
643 0         0 return $elt;
644             }
645              
646             sub empty {
647 0     0 0 0 (my $elt_in_array)=@_;
648 0 0       0 return (@{$elt_in_array} ) ? 0 : 1;
  0         0  
649             }
650              
651             # This function returns labeled items in the parse tree.
652             # It is rather aggressive in removing unlabeled items
653 16     16 0 41 sub get_tree_as_lists { (my $list) = @_;
654 16         39 my $hlist=[];
655 16         28 for my $elt (@{$list}) {
  16         46  
656 41 100 66     216 if (ref($elt) eq 'ARRAY' and scalar @{$elt}>0) { # non-empty list
  8 100       73  
657 8         14 push @{ $hlist }, get_tree_as_lists($elt);
  8         40  
658             } elsif (ref($elt) eq 'HASH') { # hash: need to process the rhs of the pair
659 15         26 (my $k, my $v) = each %{$elt};
  15         64  
660 15 100       53 if (ref($v) ne 'ARRAY') { # not an array => wrap in array and redo
    100          
661 6         11 push @{$hlist}, {$k => $v};
  6         39  
662 9         31 } elsif (@{$v}==1) { # a single-elt array
663 2         5 push @{$hlist}, {$k => $v->[0]};
  2         9  
664             } else {
665             my $pv =[
666             map {
667 16 100       82 if (ref($_) eq 'ARRAY') {
    100          
    100          
668 1         5 get_tree_as_lists($_)
669             } elsif ( ref($_) eq 'HASH') {
670 4         20 get_tree_as_lists([$_])
671 10         53 } elsif (defined $_) { $_ }
672 7         15 } @{$v}
  7         19  
673             ];
674 7         16 push @{$hlist}, {$k => $pv};
  7         36  
675             }
676             }
677             }
678 16 100       33 return scalar @{$hlist}==1 ? $hlist->[0] : $hlist;
  16         107  
679             }
680              
681 14     14 0 29 sub is_list_of_objects { (my $mlo) =$_;
682 14 100       46 if (ref($mlo) eq 'ARRAY') {
683 7         10 my @tmlo=@{$mlo};
  7         26  
684 7         16 my @l=grep { ref($_) ne 'HASH' } @tmlo;
  16         55  
685 7 100       58 return scalar(@l)?0:1;
686             } else {
687 7         35 return 0;
688             }
689             }
690              
691 5     5 0 9 sub l2m { (my $hlist) =@_;
692 5         13 my $hmap = {};
693 5         12 my @hmap_vals = map { (my $k, my $v)=%{$_}; $v } @{$hlist};
  14         22  
  14         119  
  14         49  
  5         13  
694 5         11 my @hmap_keys = map { (my $k, my $v)=%{$_}; $k } @{$hlist};
  14         17  
  14         42  
  14         42  
  5         15  
695 5 100       12 my @hmap_rvals = map { is_list_of_objects($_) ? l2m($_) : $_ } @hmap_vals;
  14         38  
696             # my @hmap_keys_vals = map { each %{$_} } @{$hlist}
697 5         15 for my $k ( @hmap_keys ) {
698 14         30 my $rv = shift @hmap_rvals;
699 14         50 $hmap->{$k}=$rv;
700             }
701             # my %{$hmap} = @hmap_keys_vals;
702 5         27 return $hmap;
703             }
704              
705            
706            
707 3     3 0 21 sub getParseTree { (my $m) =@_;
708 3         16 return l2m(get_tree_as_lists($m));
709             }
710              
711             sub run {
712 0     0 0   (my $p, my $str) = @_;
713 0           (my $st, my $rest, my $m) = apply($p,$str);
714 0           getParseTree($m);
715             }
716              
717             # return ( (hlist.length==1) ? (head hlist) : hlist) # This just returns 'false' ...
718              
719             1;
720              
721             __END__