File Coverage

blib/lib/MarpaX/Tester.pm
Criterion Covered Total %
statement 72 75 96.0
branch 15 20 75.0
condition n/a
subroutine 15 15 100.0
pod 4 4 100.0
total 106 114 92.9


line stmt bran cond sub pod time code
1             package MarpaX::Tester;
2            
3 2     2   70663 use 5.006;
  2         9  
  2         98  
4 2     2   13 use strict;
  2         5  
  2         85  
5 2     2   10 use warnings FATAL => 'all';
  2         9  
  2         94  
6 2     2   1944 use Marpa::R2;
  2         430684  
  2         94  
7 2     2   21 use Data::Dumper;
  2         5  
  2         1747  
8            
9             =head1 NAME
10            
11             MarpaX::Tester - Given a Marpa grammar and one or more test cases, generates output
12            
13             =head1 VERSION
14            
15             Version 0.02
16            
17             =cut
18            
19             our $VERSION = '0.02';
20            
21            
22             =head1 SYNOPSIS
23            
24             When working with parsers, I find test-driven development to be the quickest and most
25             effective way of getting where I want to go. (Also when working with anything else, but
26             we're talking about parsers right now.) This module does that.
27            
28             use MarpaX::Tester;
29            
30             my $r = MarpaX::Tester->new($grammar);
31             my $results = $r->test($text);
32             $results = $r->test([$text1, $text2]);
33            
34             There's also a command-line utility to produce conveniently formatted versions for
35             posting and gisting.
36            
37             The results are a hashref; "grammar" is the text of the grammar in question, "status" is
38             binary indicating OK or not OK at the grammar parsing level (i.e. has Marpa accepted the grammar
39             itself), "error" is the error raised by Marpa, if the status is false. If the error has a line and
40             column specified (not all errors do), then the text of the grammar will have an extra line inserted
41             pointing at the error with a '........^' format.
42            
43             Either way, "ver" is the Marpa version used to generate this test.
44            
45             (These are already available immediately after the grammar is defined, of course, and can be
46             retrieved at that stage with C<<$r->status>> and C<<$r->result>>, but to keep things dead simple,
47             you can still throw your test cases at an invalid grammar - they just won't be parsed, obviously.)
48            
49             So if there is a parse error, you get a structure like this:
50            
51             {
52             grammar => '...',
53             status => 0,
54             error => '...',
55             }
56            
57             If the grammar succeeds in compiling, then results are in "results", either a hashref or an array of
58             hashrefs, like this:
59            
60             {
61             grammar => '...',
62             status => 1,
63             results => {
64             test => '...',
65             status => 0,
66             error => '...',
67             },
68             }
69            
70             or conversely like this:
71            
72             {
73             grammar => '...',
74             status => 1,
75             results => {
76             test => '...',
77             status => 1,
78             parse => '...',
79             parse_val => ...
80             },
81             }
82            
83             And if you pass in a list of tests, you'll get this:
84            
85             {
86             grammar => '...',
87             status => 1,
88             results => [
89             {
90             test => '...',
91             status => 0,
92             error => '...',
93             },
94             {
95             test => '...',
96             status => 1,
97             parse => '...',
98             parse_val => ...,
99             }
100             ],
101             }
102            
103             In successful tests, you get both the parse_val (the actual value returned from the parse) and the Data::Dumper
104             text version in "parse". This makes it easy for the command-line utility to use the Template Toolkit to format
105             the results.
106            
107             =head1 METHODS
108            
109             =head2 new
110            
111             Given a string, makes a grammar and recognizer and stashes them for later use, or saves the error
112             after convenient formatting.
113            
114             =cut
115            
116             sub new {
117 2     2 1 215 my $class = shift;
118 2         9 my $self = bless {}, $class;
119 2         10 $self->{grammar} = shift;
120 2         5 my $g = $self->{grammar};
121 2         3 $self->{g} = eval { Marpa::R2::Scanless::G->new({source => \$g}); };
  2         26  
122 2 100       162752 if ($@) {
123 1         4 $self->{status} = 0;
124 1         5 $self->{error} = _format_error($@);
125 1         5 my ($line, $column) = _error_location($self->{error});
126 1 50       6 if (defined $line) {
127 1         6 $self->{grammar} = _decorate_text ($self->{grammar}, $line, $column);
128             }
129             } else {
130 1         7 $self->{status} = 1;
131             }
132 2         14 return $self;
133             }
134            
135             sub _format_error {
136 1     1   4 my $error = shift;
137 1         9 $error =~ s/Marpa::R2 exception at.*\n//gm;
138 1         8 $error =~ s/^\*.*\n//gm;
139 1         4 return $error;
140             }
141             sub _error_location {
142 1     1   2 my $error = shift;
143 1 50       8 if ($error =~ /line (\d+), column (\d+)/) {
144 1         6 return ($1, $2);
145             } else {
146 0         0 return ();
147             }
148             }
149             sub _decorate_text {
150 1     1   3 my ($text, $line, $column) = @_;
151 1         2 my $output = '';
152 1         2 my $current = 1;
153 1         5 foreach my $tline (split /\n/, $text) {
154 6         10 $output .= "$tline\n";
155 6 100       14 $output .= '.' x ($column - 1) . "^ ERROR!\n" if $current == $line;
156 6         8 $current++;
157             }
158 1         4 $output;
159             }
160            
161             =head2 test
162            
163             Tests one or more texts and returns the results in the structure described above.
164            
165             =cut
166            
167             sub test {
168 2     2 1 5 my $self = shift;
169 2 50       12 return $self->result unless $self->{status}; # Don't try if there's no recognizer.
170            
171 2         12 $self->{result} =
172             {
173             grammar => $self->{grammar},
174             ver => $Marpa::R2::VERSION,
175             status => 1,
176             result => $self->_run_test(@_),
177             }
178             }
179            
180             sub _run_test {
181 2     2   6 my $self = shift;
182 2         27 my $test = shift;
183            
184 2 100       31 return $self->_one_test ($test) unless ref($test) eq 'ARRAY';
185 1         2 my @results = ();
186 1         4 foreach my $individual (@$test) {
187 2         7 push @results, $self->_one_test($individual);
188             }
189 1         9 \@results;
190             }
191            
192             sub _one_test {
193 3     3   7 my $self = shift;
194 3         7 my $string = shift;
195            
196 3         28 my $r = Marpa::R2::Scanless::R->new({ grammar => $self->{g} });
197            
198 3         882 eval { $r->read(\$string); };
  3         24  
199 3 50       647 if ($@) {
200             return {
201 0         0 test => $string,
202             status => 0,
203             error => $@,
204             };
205             } else {
206 3         15 my $value = $r->value;
207             return {
208 3 100       3298 test => $string,
209             status => defined $value ? 1 : 0,
210             parse => _format_dump($value),
211             parse_val => $value,
212             };
213             }
214             }
215            
216             sub _format_dump { # The Dumper format doesn't appeal to me as diagnostic output.
217 3     3   7 my $dump = shift;
218 3         14 $dump = Dumper($dump);
219 3         346 $dump =~ s/\$VAR1 = \\//;
220 3         36 $dump =~ s/^ //gm;
221 3         81 $dump;
222             }
223            
224            
225             =head2 status
226            
227             Returns the BNF parse status of the original grammar (I know, two levels of parsing make it
228             hard to distinguish - this is the status of the grammar specification itself, not the status of
229             your test cases).
230            
231             =cut
232            
233 2     2 1 869 sub status { $_[0]->{status} }
234            
235             =head2 result
236            
237             Returns the last result from the test object - either the parse error or the last test set.
238            
239             =cut
240            
241             sub result {
242 2     2 1 3418 my $self = shift;
243 2 100       13 return $self->{result} if $self->{result};
244 1 50       5 if ($self->{status}) {
245             return {
246 0         0 grammar => $self->{grammar},
247             ver => $Marpa::R2::VERSION,
248             status => 1,
249             };
250             }
251             return {
252 1         10 grammar => $self->{grammar},
253             ver => $Marpa::R2::VERSION,
254             status => 0,
255             error => $self->{error},
256             };
257             }
258            
259             =head1 AUTHOR
260            
261             Michael Roberts, C<< >>
262            
263             =head1 BUGS
264            
265             Please report any bugs or feature requests to C, or through
266             the web interface at L. I will be notified, and then you'll
267             automatically be notified of progress on your bug as I make changes.
268            
269            
270            
271            
272             =head1 SUPPORT
273            
274             You can find documentation for this module with the perldoc command.
275            
276             perldoc MarpaX::Tester
277            
278            
279             You can also look for information at:
280            
281             =over 4
282            
283             =item * RT: CPAN's request tracker (report bugs here)
284            
285             L
286            
287             =item * AnnoCPAN: Annotated CPAN documentation
288            
289             L
290            
291             =item * CPAN Ratings
292            
293             L
294            
295             =item * Search CPAN
296            
297             L
298            
299             =back
300            
301            
302             =head1 ACKNOWLEDGEMENTS
303            
304            
305             =head1 LICENSE AND COPYRIGHT
306            
307             Copyright 2014 Michael Roberts.
308            
309             This program is free software; you can redistribute it and/or modify it
310             under the terms of the the Artistic License (2.0). You may obtain a
311             copy of the full license at:
312            
313             L
314            
315             Any use, modification, and distribution of the Standard or Modified
316             Versions is governed by this Artistic License. By using, modifying or
317             distributing the Package, you accept this license. Do not use, modify,
318             or distribute the Package, if you do not accept this license.
319            
320             If your Modified Version has been derived from a Modified Version made
321             by someone other than you, you are nevertheless required to ensure that
322             your Modified Version complies with the requirements of this license.
323            
324             This license does not grant you the right to use any trademark, service
325             mark, tradename, or logo of the Copyright Holder.
326            
327             This license includes the non-exclusive, worldwide, free-of-charge
328             patent license to make, have made, use, offer to sell, sell, import and
329             otherwise transfer the Package with respect to any patent claims
330             licensable by the Copyright Holder that are necessarily infringed by the
331             Package. If you institute patent litigation (including a cross-claim or
332             counterclaim) against any party alleging that the Package constitutes
333             direct or contributory patent infringement, then this Artistic License
334             to you shall terminate on the date that such litigation is filed.
335            
336             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
337             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
338             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
339             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
340             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
341             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
342             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
343             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
344            
345            
346             =cut
347            
348             1; # End of MarpaX::Tester