File Coverage

blib/lib/MarpaX/Demo/JSONParser.pm
Criterion Covered Total %
statement 109 112 97.3
branch 54 64 84.3
condition n/a
subroutine 14 14 100.0
pod 1 6 16.6
total 178 196 90.8


line stmt bran cond sub pod time code
1             package MarpaX::Demo::JSONParser;
2              
3 1     1   1184 use strict;
  1         2  
  1         39  
4 1     1   5 use warnings;
  1         2  
  1         28  
5              
6 1     1   6 use File::Basename; # For basename.
  1         33  
  1         126  
7 1     1   1337 use File::Slurp; # For read_file().
  1         17938  
  1         76  
8              
9 1     1   909 use Marpa::R2;
  1         194398  
  1         89  
10              
11 1     1   737 use MarpaX::Demo::JSONParser::Actions;
  1         3  
  1         34  
12 1     1   976 use MarpaX::Simple qw(gen_parser);
  1         9166  
  1         79  
13              
14 1     1   1465 use Moo;
  1         27764  
  1         11  
15              
16             has base_name =>
17             (
18             default => sub {return ''},
19             is => 'rw',
20             # isa => 'Str',
21             required => 0,
22             );
23              
24             has bnf_file =>
25             (
26             default => sub {return ''},
27             is => 'rw',
28             # isa => 'Str',
29             required => 1,
30             );
31              
32             has grammar =>
33             (
34             default => sub {return ''},
35             is => 'rw',
36             # isa => 'Marpa::R2::Scanless::G',
37             required => 0,
38             );
39              
40             has parser =>
41             (
42             default => sub {return ''},
43             is => 'rw',
44             # isa => 'Marpa::R2::Scanless::G',
45             required => 0,
46             );
47              
48             has scanner =>
49             (
50             default => sub {return ''},
51             is => 'rw',
52             # isa => 'Marpa::R2::Scanless::R',
53             required => 0,
54             );
55              
56             our $VERSION = '1.06';
57              
58             # ------------------------------------------------
59              
60             sub BUILD
61             {
62 60     60 0 460 my($self) = @_;
63 60         482 my $bnf = read_file($self -> bnf_file, binmode => ':utf8');
64              
65 60         36399 $self -> base_name(basename($self -> bnf_file) );
66              
67 60 100       461 if ($self -> base_name eq 'json.1.bnf')
    100          
    50          
68             {
69 20         276 $self-> grammar
70             (
71             Marpa::R2::Scanless::G -> new
72             ({
73             default_action => 'do_first_arg',
74             source => \$bnf,
75             })
76             )
77             }
78             elsif ($self -> base_name eq 'json.2.bnf')
79             {
80 20         271 $self-> grammar
81             (
82             Marpa::R2::Scanless::G -> new
83             ({
84             bless_package => 'MarpaX::Demo::JSONParser::Actions',
85             source => \$bnf,
86             })
87             )
88             }
89             elsif ($self -> base_name eq 'json.3.bnf')
90             {
91 20         108 $self-> parser
92             (
93             gen_parser
94             (
95             grammar => $bnf,
96             )
97             );
98             }
99             else
100             {
101 0         0 die "Unknown BNF. Use either 'json.[123].bnf'\n";
102             }
103              
104 60 100       5493542 if ($self -> base_name ne 'json.3.bnf')
105             {
106 40         478 $self -> scanner
107             (
108             Marpa::R2::Scanless::R -> new
109             ({
110             grammar => $self -> grammar,
111             semantics_package => 'MarpaX::Demo::JSONParser::Actions',
112             })
113             );
114             }
115              
116             } # End of BUILD.
117              
118             # ------------------------------------------------
119              
120             sub decode_string
121             {
122 2     2 0 8 my ($self, $s) = @_;
123              
124 2         16 $s =~ s/\\u([0-9A-Fa-f]{4})/chr(hex($1))/eg;
  1         9  
125 2         7 $s =~ s/\\n/\n/g;
126 2         6 $s =~ s/\\r/\r/g;
127 2         6 $s =~ s/\\b/\b/g;
128 2         7 $s =~ s/\\f/\f/g;
129 2         8 $s =~ s/\\t/\t/g;
130 2         6 $s =~ s/\\\\/\\/g;
131 2         5 $s =~ s{\\/}{/}g;
132 2         10 $s =~ s{\\"}{"}g;
133              
134 2         14 return $s;
135              
136             } # End of decode_string.
137              
138             # ------------------------------------------------
139              
140             sub eval_json
141             {
142 170     170 0 260 my($self, $thing) = @_;
143 170         616 my($type) = ref $thing;
144              
145 170 100       919 if ($type eq 'REF')
    100          
    100          
    100          
146             {
147 14         25 return \$self -> eval_json( ${$thing} );
  14         51  
148             }
149             elsif ($type eq 'ARRAY')
150             {
151 9         15 return [ map { $self -> eval_json($_) } @{$thing} ];
  12         33  
  9         261  
152             }
153             elsif ($type eq 'MarpaX::Demo::JSONParser::Actions::string')
154             {
155 102         198 my($string) = substr $thing->[0], 1, -1;
156              
157 102 100       265 return $self -> decode_string($string) if ( index $string, '\\' ) >= 0;
158 100         577 return $string;
159             }
160             elsif ($type eq 'MarpaX::Demo::JSONParser::Actions::hash')
161             {
162 20         41 return { map { $self -> eval_json( $_->[0] ), $self -> eval_json( $_->[1] ) } @{ $thing->[0] } };
  65         366  
  20         54  
163             }
164              
165 25 100       231 return 1 if $type eq 'MarpaX::Demo::JSONParser::Actions::true';
166 24 100       81 return '' if $type eq 'MarpaX::Demo::JSONParser::Actions::false';
167 21         82 return $thing;
168              
169             } # End of eval_json.
170              
171             # ------------------------------------------------
172              
173             sub parse
174             {
175 60     60 1 15579 my($self, $string) = @_;
176              
177 60 100       289 if ($self -> base_name eq 'json.3.bnf')
178             {
179 20         77 my $parse_value = $self -> parser -> ($string);
180              
181 14         137311 return $self -> post_process(@{$parse_value});
  14         101  
182             }
183             else
184             {
185 40         291 $self -> scanner -> read(\$string);
186              
187 30         105775 my($value_ref) = $self -> scanner -> value;
188              
189 30 100       10830 die "Parse failed\n" if (! defined $value_ref);
190              
191 28 100       244 $value_ref = $self -> eval_json($value_ref) if ($self -> base_name eq 'json.2.bnf');
192              
193 28         444 return $$value_ref;
194             }
195              
196             } # End of parse.
197              
198             # ------------------------------------------------
199              
200             sub post_process
201             {
202 264     264 0 487 my ($self, $type, @value) = @_;
203              
204 264 100       579 return $value[0] if $type eq 'number';
205 245 100       475 return undef if $type eq 'null';
206 243 100       898 return $value[0] if $type eq 'easy string';
207 140 100       256 return $self -> unescape($value[0]) if $type eq 'any char';
208 136 100       286 return chr(hex(substr($value[0],2))) if $type eq 'hex char';
209 135 100       235 return 1 if $type eq 'true';
210 134 100       275 return q{} if $type eq 'false';
211              
212 131 100       304 if ($type eq 'array')
213             {
214 9         13 my @result = ();
215 9         13 push @result, $self -> post_process(@{$_}) for @{$value[0]};
  9         19  
  12         34  
216              
217 9         47 return \@result;
218             }
219              
220 122 100       224 if ($type eq 'hash')
221             {
222 20         51 my %result = ();
223              
224 20         41 for my $pair (@{$value[0]})
  20         53  
225             {
226 65         80 my $key = $self -> post_process(@{$pair->[0]});
  65         198  
227 65         88 $result{$key} = $self -> post_process(@{$pair->[1]});
  65         155  
228             }
229              
230 20         116 return \%result;
231             }
232              
233 102 50       223 if ($type eq 'string')
234             {
235 102         140 return join q{}, map { $self -> post_process( @{$_} ) } @{$value[0]};
  108         113  
  108         265  
  102         194  
236             }
237              
238 0         0 die join q{ }, 'post process failed:', $type, @value;
239              
240             } # End of post_process.
241              
242             # ------------------------------------------------
243              
244             sub unescape
245             {
246 4     4 0 6 my($self, $char) = @_;
247              
248 4 50       13 return "\b" if $char eq 'b';
249 4 50       8 return "\f" if $char eq 'f';
250 4 50       9 return "\n" if $char eq 'n';
251 4 50       10 return "\r" if $char eq 'r';
252 4 50       8 return "\t" if $char eq 't';
253 4 50       9 return '/' if $char eq '/';
254 4 50       13 return '\\' if $char eq '\\';
255 4 50       16 return '"' if $char eq '"';
256              
257             # If the character is not legal, return it anyway
258             # As an alternative, we could fail here.
259              
260 0           return $char;
261              
262             } # End of unescape.
263              
264             # ------------------------------------------------
265              
266             1;
267              
268             =pod
269              
270             =head1 NAME
271              
272             C - A JSON parser with a choice of grammars
273              
274             =head1 Synopsis
275              
276             #!/usr/bin/env perl
277              
278             use strict;
279             use warnings;
280              
281             use File::ShareDir;
282              
283             use MarpaX::Demo::JSONParser;
284              
285             use Try::Tiny;
286              
287             my($app_name) = 'MarpaX-Demo-JSONParser';
288             my($bnf_name) = 'json.1.bnf'; # Or 'json.2.bnf'. See scripts/find.grammars.pl below.
289             my($bnf_file) = File::ShareDir::dist_file($app_name, $bnf_name);
290             my($string) = '{"test":"1.25e4"}';
291              
292             my($message);
293             my($result);
294              
295             # Use try to catch die.
296              
297             try
298             {
299             $message = '';
300             $result = MarpaX::Demo::JSONParser -> new(bnf_file => $bnf_file) -> parse($string);
301             }
302             catch
303             {
304             $message = $_;
305             $result = 0;
306             };
307              
308             print $result ? "Result: test => $$result{test}. Expect: 1.25e4. \n" : "Parse failed. $message";
309              
310             This script ships as scripts/demo.pl.
311              
312             You can test failure by deleting the '{' character in line 17 of demo.pl and re-running it.
313              
314             See also t/basic.tests.t for more sample code.
315              
316             =head1 Description
317              
318             C demonstrates 2 grammars for parsing JSON.
319              
320             Only 1 grammar is loaded per run, as specified by the C option to C<< new() >>.
321              
322             See t/basic.tests.t for sample code.
323              
324             =head1 Installation
325              
326             Install C as you would for any C module:
327              
328             Run:
329              
330             cpanm MarpaX::Demo::JSONParser
331              
332             or run:
333              
334             sudo cpan MarpaX::Demo::JSONParser
335              
336             or unpack the distro, and then either:
337              
338             perl Build.PL
339             ./Build
340             ./Build test
341             sudo ./Build install
342              
343             or:
344              
345             perl Makefile.PL
346             make (or dmake or nmake)
347             make test
348             make install
349              
350             =head1 Constructor and Initialization
351              
352             C is called as C<< my($parser) = MarpaX::Demo::JSONParser -> new(k1 => v1, k2 => v2, ...) >>.
353              
354             It returns a new object of type C.
355              
356             Key-value pairs accepted in the parameter list (see corresponding methods for details
357             [e.g. bnf_file([$string])]):
358              
359             =over 4
360              
361             =item o bnf_file aUserGrammarFileName
362              
363             Specify the name of the file containing your Marpa::R2-style grammar.
364              
365             See data/json.1.bnf, data/json.2.bnf and data/json.3.bnf for the cases handled by the code.
366              
367             This option is mandatory.
368              
369             Default: ''.
370              
371             =back
372              
373             =head1 Methods
374              
375             =head2 parse($string)
376              
377             Parses the given $string using the grammar whose file name was provided by the C option to
378             C<< new() >>.
379              
380             Dies if the parse fails, or returns the result of the parse if it succeeded.
381              
382             =head1 Files Shipped with this Module
383              
384             =head2 Data Files
385              
386             These JSON grammars are discussed in the L below.
387              
388             =over 4
389              
390             =item o share/json.1.bnf
391              
392             This JSON grammar was devised by Peter Stuifzand.
393              
394             =item o share/json.2.bnf
395              
396             This JSON grammar was devised by Jeffrey Kegler.
397              
398             =item o share/json.3.bnf
399              
400             This JSON grammar was devised by Jeffrey Kegler.
401              
402             =back
403              
404             =head2 Scripts
405              
406             =over 4
407              
408             =item o scripts/demo.pl
409              
410             This program is exactly what is displayed in the L above.
411              
412             Before installation of this module, run it with:
413              
414             shell> perl -Ilib scripts/demo.pl
415              
416             And after installation, just use:
417              
418             shell> perl scripts/demo.pl
419              
420             =item o scripts/find.grammars.pl
421              
422             After installation of the module, run it with:
423              
424             shell> perl scripts/find.grammars.pl (Defaults to json.1.bnf)
425             shell> perl scripts/find.grammars.pl json.1.bnf
426              
427             Or use json.2.bnf or json.2.bnf.
428              
429             It will print the name of the path to given grammar file.
430              
431             =back
432              
433             =head1 FAQ
434              
435             =head2 Where are the grammar files actually installed?
436              
437             They are not installed (when the source code is) under V 1.00.
438              
439             From V 1.01 on, I use L and L to install them.
440              
441             This a complex topic. Here are some of the issues:
442              
443             =over 4
444              
445             =item o Module::Install makes it hard to update *META.* after you update the module's version #
446              
447             It puts them in the dist but not in the current directory (alongside Makefile.PL, etc).
448              
449             =item o Install in the user's home directory, using L
450              
451             Problem: Some CPAN testers run with accounts which don't have home directories.
452              
453             I have used L when shipping modules, but that problem means I switched to L. But...
454              
455             =item o Install in a shared directory, using L
456              
457             Problem: Using L requires L during installation.
458              
459             The latter has 77 bugs on RT, although some of them may have been fixed.
460              
461             Problem: Using L requires using Makefile.PL rather that my preferred choice Build.PL.
462              
463             Sigh.
464              
465             Problem: Using L means the grammar files will be installed many directories deep.
466              
467             Again, this is something I don't like doing. On my machine, there are 13 dir names listed when I run
468             scripts/find.grammars.pl.
469              
470             Problem: Using L by itself does not support author tests.
471              
472             That needs L.
473              
474             =back
475              
476             Depite all this, for V 1.01 I've used L. And you can now run:
477              
478             shell> perl scripts/find.grammars.pl
479              
480             This reports the directory into which the grammars were installed.
481              
482             =head2 Which JSON BNF is best?
483              
484             This is not really a fair question. They were developed under different circumstances.
485              
486             =over 4
487              
488             =item o json.1.bnf is by Peter Stuifzand.
489              
490             json.1.bnf is the first attempt, when the Marpa SLIF still did not handle utf8. And it's meant to be a practical
491             grammar. The sophisticated test suite is his, too.
492              
493             =item o json.2.bnf is by Jeffrey Kegler, the author of L.
494              
495             json.2.bnf was written later, after Jeffey had a chance to study json.1.bnf. He used it to help optimise Marpa,
496             but with a minimal test suite, so it had a different purpose.
497              
498             I (Ron) converted their code into forms suitable for building this module.
499              
500             =item o json.3.bnf is by Jeffrey Kegler.
501              
502             He developed this in August, 2014, after recent significant progress in the writing of Marpa.
503              
504             =head2 Where is Marpa's Homepage?
505              
506             L.
507              
508             =head2 Are there any articles discussing Marpa?
509              
510             Yes, many by its author, and several others. See Marpa's homepage, just above, and:
511              
512             L, (in progress, by Peter Stuifzand and Ron Savage).
513              
514             L, by Peter Stuifzand.
515              
516             L, by Peter Stuifzand.
517              
518             L, by Ron Savage.
519              
520             =head1 See Also
521              
522             L.
523              
524             L.
525              
526             L.
527              
528             =head1 Machine-Readable Change Log
529              
530             The file Changes was converted into Changelog.ini by L.
531              
532             =head1 Version Numbers
533              
534             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
535              
536             =head1 Support
537              
538             Email the author, or log a bug on RT:
539              
540             L.
541              
542             =head1 Author
543              
544             L was written by Ron Savage Iron@savage.net.auE> in 2013.
545              
546             Home page: L.
547              
548             =head1 Copyright
549              
550             Australian copyright (c) 2013, Ron Savage.
551              
552             All Programs of mine are 'OSI Certified Open Source Software';
553             you can redistribute them and/or modify them under the terms of
554             The Artistic License 2.0, a copy of which is available at:
555             http://www.opensource.org/licenses/index.html
556              
557             =cut