File Coverage

blib/lib/PPIx/Literal.pm
Criterion Covered Total %
statement 60 63 95.2
branch 33 40 82.5
condition 4 6 66.6
subroutine 11 11 100.0
pod 1 1 100.0
total 109 121 90.0


line stmt bran cond sub pod time code
1              
2             package PPIx::Literal;
3             $PPIx::Literal::VERSION = '0.1.0';
4             # ABSTRACT: Convert PPI nodes into literal values
5              
6 3     3   311090 use 5.010;
  3         19  
7 3     3   13 use strict;
  3         4  
  3         56  
8 3     3   12 use warnings;
  3         3  
  3         69  
9 3     3   10 use Carp ();
  3         4  
  3         1717  
10              
11             sub convert {
12 13     13 1 37184 my $self = shift;
13 13         25 my @nodes = _prune( map { $_->clone } @_ );
  15         53  
14 13         31 return $self->_convert_nodes(@nodes);
15             }
16              
17             sub _prune {
18 13     13   1010 my @nodes;
19 13         22 for my $node (@_) {
20 15 50       48 next if $node->isa('PPI::Token::Whitespace');
21 15 100       76 $node->prune('PPI::Token::Whitespace') if $node->can('prune');
22 15         4279 push @nodes, $node;
23             }
24 13         28 return @nodes;
25             }
26              
27             sub _convert {
28 56     56   80 my ( $self, $node ) = ( shift, shift );
29              
30 56 100 100     179 if ( $node->isa('PPI::Token::Quote') && $node->can('literal') ) {
31 5         11 return $node->literal;
32             }
33 51 100       116 if ( $node->isa('PPI::Token::Quote::Double') ) {
34 2         16 $node->simplify;
35 2 50       55 return $node->literal if $node->can('literal');
36             }
37 49 100       315 if ( $node->isa('PPI::Token::Number') ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
38 10         20 return $node->literal;
39             }
40             elsif ( $node->isa('PPI::Token::QuoteLike::Words') ) {
41 2         21 return $node->literal;
42             }
43             elsif ( $node->isa('PPI::Token::Word') ) {
44 2         7 return $node->literal;
45             }
46             elsif ( $node->isa('PPI::Structure::List') ) {
47 2         33 return map { $self->_convert($_) } $node->children;
  2         16  
48             }
49             elsif ( $node->isa('PPI::Structure::Constructor') ) {
50 4         10 my @v = map { $self->_convert($_) } $node->children;
  3         16  
51 4         14 return _build_struct( $node->start->content, @v );
52             }
53             elsif ( $node->isa('PPI::Statement::Expression') ) {
54 4         7 return $self->_convert_nodes( $node->children );
55             }
56             elsif ( $node->isa('PPI::Statement') ) {
57 12 50       28 return _unknown($node) if $node->specialized;
58 12         68 return $self->_convert_nodes( $node->children );
59             }
60             elsif ( $node->isa('PPI::Document') ) {
61 12         27 return map { $self->_convert($_) } $node->children;
  11         65  
62             }
63             else {
64 1         2 return _unknown($node);
65             }
66             }
67              
68             sub _convert_nodes {
69 29     29   98 my ( $self, @nodes ) = @_;
70              
71 29         30 my @v;
72 29         33 my $expect = 'value';
73 29         77 while ( my $node = shift @nodes ) {
74 51 100       106 if ( $expect eq 'value' ) {
    50          
75 40         76 push @v, $self->_convert($node);
76 40         462 $expect = 'comma';
77             }
78             elsif ( $expect eq 'comma' ) {
79 11 50       15 if ( _is_comma($node) ) {
80 11         73 $expect = 'value';
81             }
82             else {
83             # This and the rest are considered unknowns
84 0         0 push @v, _unknown( $node, @nodes );
85 0         0 last;
86             }
87             }
88             }
89 29         97 return @v;
90             }
91              
92             sub _build_struct {
93 4     4   25 my ( $start, @values ) = @_;
94 4 100       11 if ( $start eq '{' ) {
    50          
95 2         8 return +{@values};
96             }
97             elsif ( $start eq '[' ) {
98 2         6 return [@values];
99             }
100 0         0 Carp::croak(qq{Can't build structure with start "$start"});
101             }
102              
103             sub _is_comma {
104 11     11   15 my $node = shift;
105 11   33     38 return $node->isa('PPI::Token::Operator')
106             && ( $node->content eq ',' || $node->content eq '=>' );
107             }
108              
109             sub _unknown {
110 1 50   1   4 my $content = ( @_ == 1 ) ? { node => shift } : { nodes => [@_] };
111 1         6 return bless $content, 'PPIx::Literal::Unknown';
112             }
113              
114             1;
115              
116             #pod =encoding utf8
117             #pod
118             #pod =head1 SYNOPSIS
119             #pod
120             #pod use PPI;
121             #pod use PPIx::Literal;
122             #pod
123             #pod my $doc = PPI::Document->new( \q{(1, "one", 'two')} );
124             #pod my @values = PPIx::Literal->convert($doc);
125             #pod # (1, "one", "two")
126             #pod
127             #pod my $doc = PPI::Document->new( \q{ [ 3.14, 'exp', { one => 1 } ] } );
128             #pod my @values = PPIx::Literal->convert($doc);
129             #pod # [3.14, "exp", { one => 1 }]
130             #pod
131             #pod my $doc = PPI::Document->new( \q{use zim 'Carp' => qw(carp croak)} );
132             #pod my ($use) = $doc->children;
133             #pod my @values = PPIx::Literal->convert( $use->arguments );
134             #pod # ("Carp", "carp", "croak")
135             #pod
136             #pod =head1 DESCRIPTION
137             #pod
138             #pod This code is alpha quality. It is an early release.
139             #pod Interface may change. There may be serious bugs.
140             #pod
141             #pod This module implements the conversion of a small subset of Perl
142             #pod into their literal values. The perl code to be converted
143             #pod is represented as a list of PPI nodes.
144             #pod
145             #pod The conversion works for pieces which gets built from literal tokens
146             #pod and which don't require any kind of compilation.
147             #pod
148             #pod Some examples are:
149             #pod
150             #pod 42 # number
151             #pod "a + b" # plain strings
152             #pod qw(a b c) # quoted words
153             #pod
154             #pod [] # anon array refs
155             #pod { -version => '0.3.2' }, # anon hash refs
156             #pod (2, 3, 4) # literal lists
157             #pod
158             #pod The result of the conversion is a list of Perl data structures
159             #pod which contain plain scalars and "unknowns" as leafs.
160             #pod The "unknowns" are used to represent PPI nodes which
161             #pod can't be converted to literals.
162             #pod
163             #pod =head1 METHODS
164             #pod
165             #pod L implements the following methods.
166             #pod
167             #pod =head2 convert
168             #pod
169             #pod @values = PPIx::Literal->convert(@nodes);
170             #pod
171             #pod Convert C<@nodes> into their literal values or into "unknowns".
172             #pod
173             #pod =head1 SEE ALSO
174             #pod
175             #pod L
176             #pod
177             #pod =cut
178              
179             __END__