File Coverage

blib/lib/Parse/RecDescent/Topiary.pm
Criterion Covered Total %
statement 59 66 89.3
branch 22 28 78.5
condition 2 2 100.0
subroutine 11 11 100.0
pod 2 2 100.0
total 96 109 88.0


line stmt bran cond sub pod time code
1             package Parse::RecDescent::Topiary;
2 1     1   392113 use strict;
  1         1  
  1         34  
3 1     1   6 use warnings;
  1         1  
  1         37  
4              
5             BEGIN {
6 1     1   4 use Exporter ();
  1         6  
  1         20  
7 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         104  
8 1     1   3 $VERSION = '0.05';
9 1         17 @ISA = qw(Exporter);
10 1         2 @EXPORT = qw(topiary);
11 1         2 @EXPORT_OK = qw(topiary delegation_class);
12 1         44 %EXPORT_TAGS = ( all => [qw/topiary delegation_class/] );
13             }
14              
15             =head1 NAME
16              
17             Parse::RecDescent::Topiary - tree surgery for Parse::RecDescent autotrees
18              
19             =head1 SYNOPSIS
20              
21             use Parse::RecDescent::Topiary;
22             my $parser = Parse::RecDescent->new($grammar);
23             ...
24             my $tree = topiary(
25             tree => $parser->mainrule,
26             namespace => 'MyModule::Foo',
27             ucfirst => 1
28             );
29              
30             =head1 DESCRIPTION
31              
32             L has a mechanism for automatically generating parse trees.
33             What this does is to bless each resulting node into a package namespace
34             corresponding to the rule. This might not be desirable, for a couple of
35             reasons:
36              
37             =over 4
38              
39             =item *
40              
41             You probably don't want to pollute the top-level namespace with packages,
42             and you probably don't want your grammar rules to be named according to CPAN
43             naming conventions. Also, the namespaces could collide if an application has
44             two different RecDescent grammars, that share some rule names.
45              
46             =item *
47              
48             Parse::RecDescent merely blesses the data structures. It does not call a
49             constructor. Parse::RecDescent::Topiary calls C for each class. A base
50             class, L is provided in the distribution,
51             to construct hashref style objects. The user can always supply their own -
52             inside out or whatever.
53              
54             =back
55              
56             =head2 C
57              
58             This is a function which recursively rebuilds an autotree returned by
59             L, using constructors for each node.
60              
61             This exported function takes a list of option / value pairs:
62              
63             =over 4
64              
65             =item C
66              
67             Pass in the resulting autotree returned by a Parse::RecDescent object.
68              
69             =item C
70              
71             If not specified, topiary will not use objects in the new parse tree. This
72             can be specified either as a single prefix value, or a list of namespaces
73             as an arrayref.
74              
75             As the tree is walked, each blessed node is used to form a candidate
76             class name, and if such a candidate class has a constructor, i.e. if
77             Ccan('new')> returns true, this will be used to
78             construct the new node object (see L).
79              
80             If a list of namespaces are given, each one is tried in turn, until a
81             C method is found. If no constructor is found, the node is built
82             as a data structure, i.e. it is not blessed or constructed.
83              
84             =item C
85              
86             Optional flag to upper case the first character of the rule when forming the
87             class name.
88              
89             =item C
90              
91             Optional flag that causes topiary to reduce the nesting, unambiguously, of
92             optionally quantified productions. The production foo(?) causes generation
93             of the hash entry 'foo(?)' containing an arrayref of either 0 or 1 elements
94             depending whether foo was present or not in the input string.
95              
96             If consolidate is a true value, topiary processes this entry, and either
97             generates a hash entry foo => foo_object if foo was present, or does not
98             generate a hash entry if it was absent.
99              
100             =item C
101              
102             Optional user arguments passed in. These are available to the constructors,
103             and the default constructor will put them into the new objects as
104             $self->{__ARGS__}.
105              
106             =back
107              
108             =head2 C
109              
110             @class_list = qw(Foo::Bar Foo::Baz);
111             my $class = delegation_class( 'Dongle', \@class_list, 'wiggle' );
112              
113             This subroutine is not exported by default, and is used internally by topiary.
114             C<$class> is set to C if
115             Ccan('wiggle')> or set to C if
116             Ccan('wiggle')> or return undef if no match is found.
117              
118             =head1 BUGS
119              
120             Please report bugs to http://rt.cpan.org
121              
122             =head1 AUTHOR
123              
124             Ivor Williams
125             CPAN ID: IVORW
126            
127             ivorw@cpan.org
128            
129              
130             =head1 COPYRIGHT
131              
132             This program is free software; you can redistribute
133             it and/or modify it under the same terms as Perl itself.
134              
135             The full text of the license can be found in the
136             LICENSE file included with this module.
137              
138              
139             =head1 SEE ALSO
140              
141             L.
142              
143             =cut
144              
145 1     1   797 use Params::Validate::Dummy qw();
  1         514  
  1         19  
146 1     1   753 use Module::Optional qw(Params::Validate :all);
  1         692  
  1         6  
147 1     1   863 use Scalar::Util qw(blessed reftype);
  1         2  
  1         656  
148              
149             sub topiary {
150 62     62 1 65009 my %par = validate(
151             @_,
152             { tree => 1,
153             namespace => {
154             regex => qr/\w+(\:\:\w+)*/,
155             type => SCALAR | ARRAYREF,
156             default => '',
157             },
158             ucfirst => 0,
159             args => 0,
160             consolidate => 0,
161             }
162             );
163              
164 62         597 my $tree = $par{tree};
165 62         85 my $namespace = $par{namespace};
166 62 100       172 my @ns = ref($namespace) ? @$namespace : ($namespace);
167 62         144 my $origpkg = blessed $tree;
168 62         68 my $class;
169 62 100       136 if ($origpkg) {
170 27 100       77 $origpkg = ucfirst $origpkg if $par{ucfirst};
171 27         63 $class = delegation_class( $origpkg, \@ns, 'new' );
172             }
173              
174 62   100     278 my $type = reftype($tree) || '';
175 62         59 my $rv;
176 62 50       194 if ( $type eq 'ARRAY' ) {
    100          
177 0         0 my @proto = map { topiary( %par, tree => $_ ) } @$tree;
  0         0  
178 0 0       0 if ($class) {
179 0 0       0 if ( exists $par{args} ) {
180 0         0 push @proto, __ARGS__ => $par{args};
181             }
182 0         0 $rv = $class->new(@proto);
183             }
184             else {
185 0         0 $rv = \@proto;
186             }
187             }
188             elsif ( $type eq 'HASH' ) {
189              
190             #my %proto = map { $_, topiary( %par, tree => $tree->{$_} ) }
191 27         87 my %proto = map { _consolidate_hash( $_, $tree->{$_}, \%par ) }
  59         158  
192             keys %$tree;
193 27 100       62 if ($class) {
194 21 100       78 if ( exists $par{args} ) {
195 5         8 $proto{__ARGS__} = $par{args};
196             }
197 21         95 $rv = $class->new(%proto);
198             }
199             else {
200 6         10 $rv = \%proto;
201             }
202             }
203             else {
204 35 50       65 $rv = $class ? $class->new($tree) : $tree;
205             }
206 62         463 return $rv;
207             }
208              
209             sub _consolidate_hash {
210 59     59   110 my ( $key, $tree, $args ) = @_;
211              
212 59 100       249 return $key, topiary( %$args, tree => $tree ) unless $args->{consolidate};
213 11 100       38 if ( $key =~ /(\w+)\(\?\)$/ ) {
214 2 100       15 return () unless @$tree;
215 1         6 return $1, topiary( %$args, tree => $tree->[0] );
216             }
217 9         46 return $key, topiary( %$args, tree => $tree );
218             }
219              
220             sub delegation_class {
221 27     27 1 50 my ( $node, $plist, $method ) = @_;
222              
223 27         92 for my $prefix (@$plist) {
224 37         66 my $pclass = $prefix . '::' . $node;
225 37 100       732 next unless $pclass->can($method);
226 21         72 return $pclass;
227             }
228 6         12 undef;
229             }
230              
231             1;
232              
233             # The preceding line will help the module return a true value
234