File Coverage

lib/B/DeparseTree/Node.pm
Criterion Covered Total %
statement 45 61 73.7
branch 18 26 69.2
condition 6 8 75.0
subroutine 8 11 72.7
pod 0 4 0.0
total 77 110 70.0


line stmt bran cond sub pod time code
1             # The underlying node structure of the abstract code tree built
2             # that is built.
3             # Copyright (c) 2015, 2018 Rocky Bernstein
4 3     3   19 use strict; use warnings;
  3     3   4  
  3         82  
  3         14  
  3         7  
  3         108  
5             package B::DeparseTree::Node;
6 3     3   18 use Carp;
  3         5  
  3         190  
7 3     3   55 use Config;
  3         8  
  3         143  
8             my $is_cperl = $Config::Config{usecperl};
9              
10 3     3   1594 use Hash::Util qw[ lock_hash ];
  3         8128  
  3         15  
11              
12             # Set of unary precedences
13             our %UNARY_PRECEDENCES = (
14             4 => 1, # right not
15             16 => 'sub, %, @', # "sub", "%", "@'
16             21 => '~', # steal parens (see maybe_parens_unop)
17             );
18              
19             unless ($is_cperl) {
20             lock_hash %UNARY_PRECEDENCES;
21             }
22              
23              
24             our $VERSION = '3.2.0';
25             our @ISA = qw(Exporter);
26             our @EXPORT = qw(
27             new($$$$)
28             parens_test($$$)
29             %UNARY_PRECEDENCES
30             update_other_ops($$)
31             );
32              
33             =head2 Node structure
34              
35             Fields in a node structure:
36              
37             =over
38              
39             *item B
40              
41             The string name for the node. It can be used to determine the overall
42             structure. For example a 'binop' node will have a I with a node
43             left-hand side, the string operation name and a I right-hand
44             side. Right now the type names are a little funky, but over time I
45             hope these will less so.
46              
47             * item B (optional)
48              
49             A string indicating how to separate the the strings extracted from the
50             C field. The field is subject to format expansion. In particular
51             tt can have '%;' in it to indicate we are separating statements.
52             the body.
53              
54             * item B
55              
56             A reference to a list containing either:
57              
58             =over
59              
60             * item a tuple with a strings, and a op address
61             * a DeparseTreee::Node object
62              
63             =back
64              
65             * item B
66              
67             Text representation of the node. Eventually this will diasppear
68             and, you'll use one of the node-to-string conversion routines.
69              
70             * item B
71              
72             If this node is embedded in the parent above, whether we need to add parenthesis.
73             The keys is a hash ref hash reference
74              
75             =over
76              
77             =item B
78              
79             A number passed from the parent indicating its precedence context that
80             the expression is embedded it.
81              
82             =item B
83              
84             A number as determined by the operator at this level.
85              
86             =item B
87              
88             'true' if we should to add parenthesis based on I and
89             I values; '' if not. We don't nest equal precedence
90             for unuary ops. The unary op precedence is given by
91             UNARY_OP_PRECEDENCE
92              
93             =back
94              
95             =back
96             =cut
97              
98              
99             sub parens_test($$$)
100             {
101 3185     3185 0 5356 my ($obj, $cx, $prec) = @_;
102             return ($prec < $cx
103             # Unary ops which nest just fine
104 3185   66     12963 or ($prec == $cx && !exists $UNARY_PRECEDENCES{$cx}));
105             }
106              
107             sub new($$$$$)
108             {
109 23986     23986 0 40878 my ($class, $op, $deparse, $data, $sep, $type, $opts) = @_;
110 23986         27962 my $addr = -1;
111 23986 100       40217 if (ref($op)) {
112 21332 50       31553 if (ref($op) eq 'B::DeparseTree') {
113             # use Enbugger 'trepan'; Enbugger->stop;
114 0         0 Carp::confess("Rocky got the order of \$self, and \$op confused again");
115 0         0 $addr = -2;
116             } else {
117 21332         24297 eval { $addr = $$op };
  21332         28636  
118             }
119             }
120 23986         85357 my $self = bless {
121             addr => $addr,
122             op => $op,
123             deparse => $deparse,
124             type => $type,
125             }, $class;
126              
127 23986 100       44626 $self->{sep} = $sep if defined $sep;
128 23986 100       39303 if (ref($data)) {
    50          
129             # Passed in a ref ARRAY
130 12689         20197 $self->{texts} = $data;
131 12689 100       19486 $self->{text} = $deparse->combine2str($sep, $data) if defined $sep;
132             } elsif (defined $data) {
133             # Passed in a string
134 11297         18804 $self->{text} = $data;
135             } else {
136             # Leave {text} and {texts} uninitialized
137             }
138              
139 23986         34591 foreach my $optname (qw(other_ops parent_ops child_pos maybe_parens
140             omit_next_semicolon position)) {
141 143916 100       205917 $self->{$optname} = $opts->{$optname} if $opts->{$optname};
142             }
143 23986 100       33783 if ($opts->{maybe_parens}) {
144 1592         2532 my ($obj, $context, $precedence) = @{$opts->{maybe_parens}};
  1592         3594  
145 1592         3362 my $parens = parens_test($obj, $context, $precedence);
146             $self->{maybe_parens} = {
147             context => $context,
148             precedence => $precedence,
149 1592 100       8472 force => $obj->{'parens'},
150             parens => $parens ? 'true' : ''
151             };
152 1592 50 66     4358 $self->{text} = "($self->{text})" if exists $self->{text} and $parens;
153             }
154 23986 50       35654 if ($opts->{prev_expr}) {
155 0         0 $self->{prev_expr} = $opts->{prev_expr};
156             }
157 23986         55417 return $self;
158             }
159              
160             # Possibly add () around $text depending on precedence $prec and
161             # context $cx. We return a string.
162             sub maybe_parens($$$$)
163             {
164 0     0 0 0 my($self, $info, $cx, $prec) = @_;
165 0 0       0 if (parens_test($info, $cx, $prec)) {
166 0         0 $info->{text} = $self->combine('', "(", $info->{text}, ")");
167             # In a unop, let parent reuse our parens; see maybe_parens_unop
168 0 0       0 if ($cx == 16) {
169 0         0 $info->{parens} = 'reuse';
170             } else {
171 0         0 $info->{parens} = 'true';
172             }
173 0         0 return $info->{text};
174             } else {
175 0         0 $info->{parens} = '';
176 0         0 return $info->{text};
177             }
178             }
179              
180             # Update $self->{other_ops} to add $info
181             sub update_other_ops($$)
182             {
183 5209     5209 0 7967 my ($self, $info) = @_;
184 5209   100     13990 $self->{other_ops} ||= [];
185 5209         6730 my $other_ops = $self->{other_ops};
186 5209         5408 push @{$other_ops}, $info;
  5209         7957  
187 5209         9043 $self->{other_ops} = $other_ops;
188             }
189              
190             # Demo code
191             unless(caller) {
192             my $old_pkg = __PACKAGE__;
193             package B::DeparseTree::NodeDemo;
194             sub new($) {
195 0     0     my ($class) = @_;
196 0           bless {}, $class;
197             }
198             sub combine2str($$$) {
199 0     0     my ($self, $sep, $data) = @_;
200 0           join($sep, @$data);
201             }
202             my $deparse = __PACKAGE__->new();
203             my $node = $old_pkg->new('op', $deparse, ['X'], 'test', {});
204             print $node->{text}, "\n";
205             }
206             1;