File Coverage

web/cgi-bin/yatt.lib/YATT/LRXML/EntityPath.pm
Criterion Covered Total %
statement 109 116 93.9
branch 69 90 76.6
condition 22 32 68.7
subroutine 12 12 100.0
pod 0 3 0.0
total 212 253 83.7


line stmt bran cond sub pod time code
1             # -*- mode: perl; coding: utf-8 -*-
2             package YATT::LRXML::EntityPath;
3 4     4   593 use strict;
  4         7  
  4         164  
4 4     4   14 use warnings FATAL => qw(all);
  4         5  
  4         170  
5 4     4   13 BEGIN {require Exporter; *import = \&Exporter::import}
  4         6840  
6             our @EXPORT_OK = qw(parse_entpath is_nested_entpath);
7             our @EXPORT = @EXPORT_OK;
8              
9             =pod
10              
11             term ::= ( text | expr | pipeline ) ','? ;
12              
13             pipeline ::= container? trail+ ;
14              
15             trail ::= var | '[' term ']' ;
16              
17             container::= '[' term* ']'
18             | '{' ( dot_name (':' text | '=' term )
19             | other+ ','?
20             )* '}' ;
21              
22             var ::= (':'+ | '.'+) name ( '(' term* ')' )? ;
23             name ::= \w+ ;
24             dot_name ::= [\w\.]+ ;
25              
26             expr ::= '=' text ;
27             text ::= word ( group word? )* ; -- group で始まるのは、container.
28              
29             group ::= [\(\[\{] ( text | ',' )* [\}\]\)]
30              
31             word ::= [\w\$\-\+\*/%<>] other* ;
32             other ::= [\w\$\-\+\*/%<>:\.!=] ;
33              
34             =cut
35              
36             # is_nested_entpath($entpath, ?head?)
37              
38             sub is_nested_entpath {
39 50 50 33 50 0 177 return unless defined $_[0] and ref $_[0] eq 'ARRAY';
40 50         42 my $item = shift;
41 50 100 66     314 return unless defined $item->[0] and ref $item->[0] eq 'ARRAY';
42 2 50       14 return 1 unless defined $_[0];
43 0 0       0 defined $item->[0][0] and $item->[0][0] eq $_[0];
44             }
45              
46             our ($TRANS, $NODE, $ORIG);
47              
48             sub mydie (@) {
49 2     2 0 4 my $fmt = shift;
50 2         2 my $diag = do {
51 2 50 33     10 if ($TRANS and $NODE) {
52 2         11 $TRANS->node_error($NODE, $fmt, @_);
53             } else {
54 0         0 sprintf $fmt, @_;
55             };
56             };
57 2         32 die $diag;
58             }
59              
60             sub parse_entpath {
61 272     272 0 13094 my ($pack, $orig, $trans, $node) = @_;
62 272 100       599 return undef unless defined $orig;
63 271         420 local $_ = $orig;
64 271         308 local $ORIG = $orig;
65 271         299 local $TRANS = $trans;
66 271         277 local $NODE = $node;
67 271         219 my @result;
68 271 50       361 if (wantarray) {
69 271         414 @result = &_parse_pipeline;
70             } else {
71 0         0 $result[0] = &_parse_pipeline;
72             }
73 270 100       600 if ($_ ne '') {
74 1         6 mydie "Unexpected token '$_' in entpath '$orig'";
75             }
76 269 50       901 wantarray ? @result : $result[0];
77             }
78              
79             my %open_head = qw| ( call [ array { hash |;
80             my %open_rest = qw| ( call [ aref |;
81             my %close_ch = qw( ( ) [ ] { } );
82              
83             my $re_var = qr{[:]+ (\w+) (\()?}x;
84             my $re_other= qr{[\w\$\-\+\*/%<>\.=\@\|!:]}x;
85             my $re_word = qr{[\w\$\-\+\*/%<>\.=\@\|!] $re_other*}x;
86              
87             sub _parse_pipeline {
88 316     316   273 my @pipe;
89 316 100       1144 if (s/^ \[ //x) {
    100          
90             # container
91 6         20 push @pipe, _parse_group(['array'], ']', \&_parse_term);
92             } elsif (s/^ \{ //x) {
93 17         21 push @pipe, &_parse_hash;
94             }
95 316         2271 while (s/^$re_var | ^(\[) | ^(\{)//x) {
96 361 100       1097 if ($2) {
    100          
    100          
    50          
97             # '('
98 96         390 push @pipe, _parse_group([call => $1], ')', \&_parse_term);
99             } elsif (defined $1) {
100             # \w+
101 239         1107 push @pipe, [var => $1];
102             } elsif (defined $3) { # '['
103 24         70 push @pipe, _parse_group(['aref'], ']', \&_parse_term, 'expr');
104             } elsif (defined $4) {
105 2         8 push @pipe, _parse_group(['var'], '}', \&_parse_term);
106             } else {
107 0         0 mydie "?? $_";
108             }
109             }
110 315 50       799 wantarray ? @pipe : \@pipe;
111             }
112              
113             my $re_grend = qr{ (?=[\)\]\}]) | $ }x;
114             my $re_text = qr{($re_word) # 1
115             (?: ([\(\[\{]) # 2
116             | $re_grend)?
117             | $re_grend
118             }x;
119              
120             sub _parse_term {
121 169     169   145 my ($literal_type) = @_;
122 169   100     411 $literal_type ||= 'text';
123             # :foo() [call => foo]
124             # :foo(,) [call => foo => [text => '']]
125             # :foo(bar) [call => foo => [text => 'bar']]
126             # :foo(,,) [call => foo => [text => ''], [text => '']]
127             # :foo(bar,) [call => foo => [text => 'bar'], [text => '']]
128             # :foo(bar,,)[call => foo => [text => 'bar'], [text => '']]
129 169 100       239 if (s{^,}{}x) {
130 8         18 return [$literal_type => ''];
131             }
132 161 100       286 if (my $is_expr = s{^=}{}) {
133 12         20 return &_parse_expr;
134             }
135 149         111 my @result;
136 149 100       1409 unless (s{^$re_text}{}) {
137 45         68 @result = &_parse_pipeline;
138             } else {
139 104         103 my $result = '';
140             TEXT: {
141 104         78 do {
  104         90  
142 165 100       327 $result .= $1 if defined $1;
143 165 50       231 $result .= $4 if defined $4;
144 165 100 66     1979 if (my $opn = $2 || $3) {
    100 66        
145             # open group
146 4         5 $result .= $opn;
147 4         11 $result .= &_parse_group_string($close_ch{$opn});
148             } elsif (not defined $1 and not defined $4) {
149 59         98 last TEXT;
150             }
151             } while s{^(?: $re_text | ([\(\[\{]) | ([:\.]) ) }{}x;
152             }
153 103         331 @result = [$literal_type => $result];
154             }
155 148         199 s/^,//;
156 148         218 @result;
157             }
158              
159             sub _parse_expr {
160 12     12   32 my $literal_type = 'expr';
161 12 50       30 if (s{^,}{}x) {
162 0         0 return [$literal_type => ''];
163             }
164 12         13 my $result = '';
165             TEXT:
166 12         636 while (s{^(?: $re_text | ([\(\[\{]) | ([:\.]) ) }{}x) {
167 27 100       210 $result .= $1 if defined $1;
168 27 100       42 $result .= $4 if defined $4;
169 27 100 66     149 if (my $opn = $2 || $3) {
    100 100        
170             # open group
171 12         17 $result .= $opn;
172 12         26 $result .= &_parse_group_string($close_ch{$opn});
173             } elsif (not defined $1 and not defined $4) {
174 4         8 last TEXT;
175             }
176             }
177 12         26 s/^,//;
178 12         32 [$literal_type => $result];
179             }
180              
181             sub _parse_group {
182 128     128   187 my ($group, $close, $sub, @rest) = @_;
183 128         393 for (my ($len, $cnt) = length($_); $_ ne ''; $len = length($_), $cnt++) {
184 271 100       614 if (s/^ ([\)\]\}])//x) {
185 127 50       230 mydie "Paren mismatch: expect $close got $1 " if $1 ne $close;
186 127         130 last;
187             }
188 144         241 my @pipe = $sub->(@rest);
189 143 50 66     310 if ($cnt && $len == length($_)) {
190 0 0       0 mydie "Can't match: $_" . (defined $close ? " for $close" : "");
191             }
192 143 100       422 push @$group, @pipe <= 1 ? @pipe : \@pipe;
193             }
194 127         469 $group;
195             }
196              
197             sub _parse_group_string {
198 20     20   22 my ($close) = @_;
199 20         16 my $result = '';
200 20         47 for (my ($len, $prev) = length($_); $_ ne ''
201             ; $prev = $len, $len = length($_)) {
202 47 100       85 if (s/^ ([\)\]\}])//x) {
203 19 50       36 mydie "Paren mismatch: expect $close got $1 " if $1 ne $close;
204 19         18 $result .= $1;
205 19         17 last;
206             }
207 28 100       634 if (s/^($re_word | , )//x) {
    100          
208 22         32 $result .= $1;
209             } elsif (s/^([\(\[\{])//) {
210 4         7 $result .= $1;
211 4         12 $result .= &_parse_group_string($close_ch{$1});
212             }
213 28 100 100     235 if (defined $prev and $prev == length($_)) {
214 1         6 mydie "Can't parse entity_path group $ORIG (near $_)\n"
215             }
216             }
217 19         76 $result;
218             }
219              
220             sub _parse_hash {
221 17     17   20 my @hash = ('hash');
222 17         29 for (my ($len, $cnt) = length($_); $_ ne ''; $len = length($_), $cnt++) {
223 42 100       73 if (s/^ ([\)\]\}])//x) {
224 17 50       30 mydie "Paren mismatch: expect \} got $1 " if $1 ne '}';
225 17         10 last;
226             }
227             # {!=,:var} を許すには…
228 25 50 66     188 if (s/^([\w\.\-]+) [:=] //x || s/^($re_other+) ,?//x) {
229             # ↑ array でも許すべきか?
230 25         27 my $str = $1;
231 25 50       50 push @hash, [$str =~ s/^:// ? 'var' : 'text', $str];
232             }
233 25         34 my @value = &_parse_term;
234 25 100       42 push @hash, @value > 1 ? \@value : $value[0];
235 25 50       58 unless (length($_) < $len) {
236 0         0 mydie "Infinite loop on parse_hash: $_";
237             }
238             }
239             # XXX: Give more detailed diag!
240 17 50       32 mydie "Odd number of hash elements" if (@hash - 1) % 2;
241 17         24 \@hash;
242             }
243              
244             1;