File Coverage

blib/lib/ABNF/Validator.pm
Criterion Covered Total %
statement 197 231 85.2
branch 56 114 49.1
condition 7 18 38.8
subroutine 47 49 95.9
pod n/a
total 307 412 74.5


line stmt bran cond sub pod time code
1             package ABNF::Validator;
2              
3             =pod
4              
5             =head1 NAME
6              
7             ABNF::Validator - class to verify strings based on ABNF-grammars
8              
9             =head1 DESCRIPTION
10              
11             =head1 METHODS
12              
13             =cut
14              
15 3     3   2521 use 5.014;
  3         12  
  3         117  
16              
17 3     3   20 use strict;
  3         6  
  3         104  
18 3     3   17 use warnings;
  3         7  
  3         101  
19 3     3   17 use re 'eval';
  3         5  
  3         104  
20              
21 3     3   17 use Carp;
  3         6  
  3         187  
22 3     3   19 use Readonly;
  3         6  
  3         128  
23 3     3   16 use Method::Signatures;
  3         6  
  3         23  
24 3     3   1309 use Data::Dumper;
  3         7  
  3         162  
25              
26 3     3   17 use Parse::ABNF;
  3         8  
  3         87  
27              
28 3     3   18 use ABNF::Grammar qw(splitRule $BASIC_RULES);
  3         4  
  3         435  
29              
30 3     3   22 use base qw(Exporter);
  3         7  
  3         722  
31              
32             our @EXPORT_OK = qw(Validator);
33              
34             Readonly my $ARGUMENTS_RULES => "generic_arguments_rule_for_";
35              
36             Readonly my $CLASS_MAP => {
37             Choice => \&_choice,
38             Group => \&_group,
39             Range => \&_range,
40             Reference => \&_reference,
41             Repetition => \&_repetition,
42             Rule => \&_rule,
43             String => \&_string,
44             Literal => \&_literal,
45             ProseValue => \&_proseValue
46             };
47              
48             =pod
49              
50             =head1 ABNF::Validator->C<new>($grammar)
51              
52             Creates a new B<ABNF::Validator> object.
53              
54             $grammar isa B<ABNF::Grammar>.
55              
56             =cut
57              
58 3 50 33 3   8022 method new(ABNF::Grammar $grammar) {
  3 50   3   8  
  3 50       16  
  3         60  
  3         803  
  3         17  
59              
60 3   33     22 my $class = ref($self) || $self;
61              
62 3         227 $self = { _grammar => $grammar };
63              
64 3         12 bless($self, $class);
65              
66 3         17 $self->_init();
67              
68 3         21 return $self;
69             }
70              
71 3 50   3   2050 method _init() {
  3     3   7  
  3         14  
72 3         35 my $commands = $self->{_grammar}->commands();
73 3         8 $self->{_commandsPattern} = do {
74 3         12 my $pattern = join(" | ", @$commands);
75 3         561 qr/\A (?: $pattern ) \Z/ix;
76             };
77              
78 3         31 $self->{_rules} = _value([
79             values($self->{_grammar}->rules()),
80             values($BASIC_RULES)
81             ]);
82              
83 3         23 $self->{_regexps} = do {
84 3     3   8658 use Regexp::Grammars;
  3         76776  
  3         30  
85              
86 3         9 my %res = ();
87 3         13 foreach my $token ( @$commands ) {
88             # command
89 15         76 my $str = "
90             #<logfile: /dev/null>
91              
92             ^ <" . _fixRulename($token) . "> \$
93              
94             $self->{_rules}
95             ";
96 15         276 $res{$token} = qr{$str }ixs;
97              
98             # arguments
99 15         340 my $value = $self->{_grammar}->rule($token);
100 15         138 my $name = _fixRulename($ARGUMENTS_RULES . $token);
101 15         100 my $rule = {class => "Rule", name => $name};
102 15         85 my $val = (splitRule($value))[-1];
103              
104 15 100       163 if ( $value->{value} != $val ) {
105 6         19 $rule->{value} = $val;
106 6         33 my $converted = _value($rule);
107 6         63 $res{$name} = qr{
108             ^ <$name> $
109              
110             $converted
111              
112             $self->{_rules}
113             }xis;
114             }
115             }
116              
117 3         27 \%res;
118             };
119             }
120              
121 3 50   3   24000 func _value($val, $dent = 0) {
  288 100   288   727  
  288 50       355  
  288         517  
  288         572  
122              
123 288 100 33     1486 if ( UNIVERSAL::isa($val, 'ARRAY') ) {
    50          
124 24         68 return join('', map { _value($_ , $dent) } @$val);
  108         205  
125             } elsif ( UNIVERSAL::isa($val, 'HASH') && exists($CLASS_MAP->{ $val->{class} }) ) {
126 264         2081 return $CLASS_MAP->{ $val->{class} }->($val, $dent);
127             } else {
128 0         0 croak "Unknown substance " . Dumper($val);
129             }
130             }
131              
132              
133 3 50   3   9010 func _choice($val, $dent) {
  27 50   27   178  
  27 50       35  
  27         68  
  27         35  
  27         52  
134 27         40 return "(?: " . join(' | ', map { _value($_ , $dent + 1) } @{$val->{value}}) . ")";
  72         157  
  27         69  
135             }
136              
137 3 50   3   9617 func _group($val, $dent) {
  21 50   21   142  
  21 50       31  
  21         55  
  21         27  
  21         59  
138 21         69 return '(?: ' . _value($val->{value}, $dent + 1) . ' )';
139             }
140              
141 3 50   3   8246 func _reference($val, $dent) {
  57 50   57   315  
  57 50       77  
  57         111  
  57         61  
  57         112  
142 57         128 return "<" . _fixRulename($val->{name}) . ">";
143             }
144              
145 3 50   3   8059 func _repetition($val, $dent) {
  6 50   6   47  
  6 50       11  
  6         27  
  6         10  
  6         22  
146              
147 3     3   422 no warnings 'uninitialized';
  3         7  
  3         626  
148 6         33 my %maxMin = (
149             # max min
150             "1 0" => '?',
151             " 0" => '*',
152             " 1" => '+',
153             );
154              
155 6 50       37 if ( my $mm = $maxMin{"$val->{max} $val->{min}"} ) {
    0          
156 6         27 return " (?: " . _value($val->{value}, $dent + 1) . " )$mm ";
157             } elsif( $val->{min} == $val->{max} ){
158 0         0 return " (?: ". _value($val->{value}, $dent + 1) . " ){$val->{max}} ";
159             } else {
160 0         0 return " (?: " . _value($val->{value}, $dent+1) . " ){$val->{min}, $val->{max}} ";
161             }
162             }
163              
164 3 50   3   7442 func _rule($val, $dent) {
  72 50   72   429  
  72 50       90  
  72         142  
  72         83  
  72         162  
165 72         92 my $ret = "";
166 72         141 my $name = $val->{name};
167              
168 72 50       176 if ( 'ws' eq lc($name) ) {
169 0         0 warn "Changing rule ws to token to avoid 'infinitely recursive unpleasantness.'\n";
170 0         0 $ret .= "<rule: ws>\n "; # may be token
171             } else {
172 72         161 $ret .= "<token: " . _fixRulename($val->{name}) . ">\n ";
173             }
174 72         215 $ret .= _value($val->{value}, $dent + 1);
175 72         271 $ret . "\n\n";
176             }
177              
178             #~ @{[_fixRulename($$v{name})]}
179 3 50   3   6148 func _fixRulename($name) {
  181 50   181   691  
  181         261  
  181         368  
180 181         366 $name =~ s/[-\W]/_/g;
181 181         720 $name;
182             }
183              
184 3 50   3   10485 func _range($val, $dent) {
  21 50   21   131  
  21 50       27  
  21         48  
  21         28  
  21         46  
185 21         28 my $ret = "";
186 21         25 $ret .= '[';
187 21         33 given ( $val->{type} ) {
188 21         54 when ( 'hex' ) {
189 21         37 $ret .= join('-', map { '\x{' . $_ . '}' } $val->{min}, $val->{max});
  42         125  
190             }
191 0         0 when ( 'binary' ) {
192 0         0 $ret .= join('-', map { sprintf('\\%o', oct("0b$_")) } $val->{min}, $val->{max});
  0         0  
193             }
194 0         0 when ( 'decimal' ) {
195 0         0 $ret .= join('-', map { sprintf('\\%o', $_) } $val->{min}, $val->{max});
  0         0  
196             }
197 0         0 default {
198 0         0 croak "## Range type $val->{type} $val->{value} \n";
199             }
200             }
201 21         39 $ret .= "]";
202 21         63 $ret;
203             }
204              
205 3 50   3   11885 func _string($val, $dent) {
  18 50   18   111  
  18 50       23  
  18         42  
  18         24  
  18         44  
206 18         25 my $ret = "";
207 18         34 given ( $val->{type} ) {
208 18         78 when ( 'hex' ) {
209 18         23 $ret = join('', map { '\x' . $_ } @{$val->{value}});
  18         71  
  18         49  
210             }
211 0         0 when ( 'binary' ) {
212 0         0 $ret .= join('', map { sprintf('\\%o', oct("0b$_")) } @{$val->{value}});
  0         0  
  0         0  
213             }
214 0         0 when ( 'decimal' ) {
215 0         0 $ret .= join('', map { sprintf('\\%o', $_) } @{$val->{value}});
  0         0  
  0         0  
216             }
217 0         0 default {
218 0         0 die "## String type $val->{type} $val->{value} \n";
219             }
220             #~ warn "##", map({ "$_ ( $val->{$_} ) " } sort keys %$val ), "\n";
221             }
222             #~ " $ret ";
223 18         62 $ret;
224             }
225              
226 3 50   3   10653 func _literal($val, $dent) {
  42 50   42   241  
  42 50       46  
  42         85  
  42         49  
  42         80  
227 42         200 return quotemeta($val->{value});
228             }
229              
230 3 0   3   7494 func _proseValue($val, $dent) {
  0 0   0   0  
  0 0       0  
  0         0  
  0         0  
  0         0  
231 0         0 return "<" . _fixRulename($val->{value}) . ">";
232             }
233              
234             =pod
235              
236             =head1 $validator->C<validate>($rule, $string)
237              
238             Return 1 if $string matches $rule and 0 otherwise.
239              
240             $rule is rulename.
241              
242             $string is arguments string.
243              
244             dies if there is no command like $rule.
245              
246             =cut
247              
248 3 50 33 3   9779 method validate(Str $rule, Str $string) {
  8 50 33 8   773  
  8 50       29  
  8 50       79  
  8 50       19  
  8         47  
  8         63  
  8         18  
  8         23  
249 8 100       239 croak "Unexisted command $rule" unless exists($self->{_regexps}->{$rule});
250 7         272 scalar($string =~ $self->{_regexps}->{$rule});
251             }
252              
253             =pod
254              
255             =head1 $validator->C<validateArguments>($rule, $string)
256              
257             Return 1 if $string matches arguments rules form $rule and 0 otherwise.
258              
259             $rule is rulename.
260              
261             $string is arguments string.
262              
263             dies if there is no command like $rule.
264              
265             =cut
266              
267              
268 3 50   3   9176 method validateArguments($rule, $string) {
  23 50   23   572  
  23 50       55  
  23         30  
  23         38  
  23         26  
  23         44  
269 23 100       207 croak "Unexisted command $rule" unless exists($self->{_regexps}->{$rule});
270 22         59 my $args = _fixRulename($ARGUMENTS_RULES . $rule);
271 22   66     317 scalar(exists($self->{_regexps}->{$args}) && ($string =~ $self->{_regexps}->{$args}));
272             }
273              
274             =pod
275              
276             =head1 $validator->C<validateCommand>($command)
277              
278             Return 1 if there exists command like $command and 0 otherwise
279              
280             =cut
281              
282 3 50   3   6018 method validateCommand($command) {
  3 50   3   476  
  3         14  
  3         6  
  3         7  
283 3         49 return $command =~ $self->{_commandsPattern};
284             }
285              
286             =pod
287              
288             =head1 $validator->C<hasCommand>($command)
289              
290             Return 1 if there exists command like $command and 0 otherwise
291              
292             =cut
293              
294 3 0   3   5831 method hasCommand($command) {
  0 0   0   0  
  0         0  
  0         0  
  0         0  
295 0         0 return exists($self->{_regexps}->{$command});
296             }
297              
298             =pod
299              
300             =head1 FUNCTIONS
301              
302             =head1 C<Validator>()
303              
304             Return __PACKAGE__ to reduce class name :3
305              
306             =cut
307              
308 3 50   3   2038 func Validator() {
  3     3   47  
309 3         34 return __PACKAGE__;
310             }
311              
312             1;
313              
314             =pod
315              
316             =head1 AUTHOR / COPYRIGHT / LICENSE
317              
318             Copyright (c) 2013 Arseny Krasikov <nyaapa@cpan.org>.
319              
320             This module is licensed under the same terms as Perl itself.
321              
322             =cut