File Coverage

blib/lib/Attean/TreeRewriter.pm
Criterion Covered Total %
statement 83 88 94.3
branch 29 34 85.2
condition 5 5 100.0
subroutine 9 9 100.0
pod 2 2 100.0
total 128 138 92.7


line stmt bran cond sub pod time code
1 50     50   602 use v5.14;
  50         155  
2 50     50   285 use warnings;
  50         145  
  50         2315  
3              
4             =head1 NAME
5              
6             Attean::TreeRewriter - Walk and rewrite subtrees
7              
8             =head1 VERSION
9              
10             This document describes Attean::TreeRewriter version 0.033
11              
12             =head1 SYNOPSIS
13              
14             use v5.14;
15             use Attean;
16             my $w = Attean::TreeRewriter->new();
17             my ($rewritten, $tree) = $w->rewrite($tree, $thunk);
18             if ($rewritten) {
19             ...
20             }
21              
22             =head1 DESCRIPTION
23              
24             The Attean::TreeRewriter class walks the nodes of query trees and rewrites
25             sub-trees based on handlers that have been registered prior to rewriting.
26              
27             =head1 ROLES
28              
29             None.
30              
31             =head1 METHODS
32              
33             =over 4
34              
35             =cut
36              
37             use Moo;
38 50     50   279 use Types::Standard qw(CodeRef ArrayRef Str);
  50         130  
  50         267  
39 50     50   14220 use Scalar::Util qw(blessed refaddr);
  50         113  
  50         350  
40 50     50   30708 use namespace::clean;
  50         103  
  50         2484  
41 50     50   314 with 'MooX::Log::Any';
  50         121  
  50         332  
42              
43             has types => (is => 'rw', isa => ArrayRef[Str], default => sub { ['Attean::API::DirectedAcyclicGraph'] });
44             has pre_handlers => (is => 'rw', isa => ArrayRef[CodeRef], default => sub { [] });
45            
46             =item C<< register_pre_handler( \&code ) >>
47              
48             Register a handler that will be called for each sub-tree during tree rewriting.
49              
50             The function will be called as C<< &code( $tree, $parent_node, $thunk ) >> where
51             C<< $thunk >> is an opaque value passed to C<< rewrite >>.
52              
53             The function must return a list C<< ($handled, $descend, $rewritten) >>.
54             C<< $handled >> is a boolean indicating whether the handler function rewrote
55             the sub-tree, which is returned as C<< $rewritten >>. The C<< $descend >>
56             boolean value indicates whether the the tree rewriting should continue downwards
57             in the tree.
58              
59             =cut
60              
61             my $self = shift;
62             my $code = shift;
63 12     12 1 309 push(@{ $self->pre_handlers }, $code);
64 12         19 }
65 12         27
  12         190  
66             my $self = shift;
67             my ($t, $parent, $thunk) = @_;
68             my $main_descend = 0;
69 41     41   48 foreach my $cb (@{ $self->pre_handlers }) {
70 41         62 my ($handled, $descend, $rewritten) = $cb->($t, $parent, $thunk);
71 41         47 unless (defined($descend)) {
72 41         52 $descend = 1;
  41         522  
73 41         226 }
74 41 50       1059 if ($handled) {
75 0         0 return ($descend, $rewritten);
76             } elsif ($descend) {
77 41 100       93 $main_descend = 1;
    50          
78 9         22 }
79             }
80 32         43 return ($main_descend, undef);
81             }
82              
83 32         57 =item C<< rewrite( $tree, $thunk, \%seen, $parent ) >>
84              
85             Rewrites the given C<< $tree >> using the registered handler functions.
86             C<< $thunk >> is passed through to each handler function.
87             C<< %seen >> is currently unused.
88             C<< $parent >> is passed through to the handler functions as the value of the
89             pseudo-parent tree node for C<< $tree >>.
90              
91             Returns a list C<< ($handled, $tree) >> with C<< $handled >> indicating whether
92             rewriting was performed, with the corresponding rewritten C<< $tree >>.
93              
94             =cut
95              
96             my $self = shift;
97             my $tree = shift;
98             my $thunk = shift;
99             my $seen = shift || {};
100 58     58 1 1839 my $parent = shift;
101 58         73 my $ok = 0;
102 58         59 # if ($seen->{ refaddr($tree) }++) {
103 58   100     129 # return (0, $tree);
104 58         66 # }
105 58         62 foreach my $type (@{ $self->types }) {
106             if (blessed($tree) and $tree->does($type)) {
107             $ok++;
108             }
109 58         58 }
  58         823  
110 113 100 100     1165 unless ($ok) {
111 41         553 $self->log->debug(ref($tree) . ' does not conform to any rewrite roles');
112             return (0, $tree);
113             }
114 58 100       363
115 17         222 my ($descend, $rewritten) = $self->_fire_pre_handlers($tree, $parent, $thunk);
116 17         2997 if ($rewritten) {
117             if (refaddr($rewritten) == refaddr($tree)) {
118             return (0, $tree);
119 41         71 }
120 41 100       71 if ($descend) {
121 9 50       32 (undef, my $rewritten2) = $self->rewrite($rewritten, $thunk, $seen, $parent);
122 0         0 my $changed = (refaddr($rewritten) != refaddr($rewritten2));
123             return ($changed, $rewritten2);
124 9 50       15 } else {
125 0         0 return (1, $rewritten);
126 0         0 }
127 0         0 }
128             if ($descend) {
129 9         53 my @children;
130             my %attributes;
131             my $changed = 0;
132 32 50       55 if ($tree->does('Attean::API::DirectedAcyclicGraph')) {
133 32         37 my @c = @{ $tree->children };
134             foreach my $i (0 .. $#c) {
135 32         40 my $p = $c[$i];
136 32 100       60 my ($childchanged, $child) = $self->rewrite($p, $thunk, $seen, $tree);
137 28         299 push(@children, $childchanged ? $child : $p);
  28         79  
138 28         66 if ($childchanged) {
139 17         25 $self->log->debug("Child $p changed for parent $tree");
140 17         36 $changed = 1;
141 17 100       36 }
142 17 100       38 }
143 11         134 }
144 11         204
145             if ($tree->can('tree_attributes')) {
146             foreach my $attr ($tree->tree_attributes) {
147             my $p = $tree->$attr();
148             if (ref($p) eq 'ARRAY') {
149 32 100       180 my @patterns;
150 24         352 foreach my $pp (@$p) {
151 28         74 # warn "- $attr: $pp\n";
152 28 100       50 my ($childchanged, $child) = $self->rewrite($pp, $thunk, $seen, $tree);
153 6         6 if ($childchanged) {
154 6         15 $changed = 1;
155             }
156 7         31 push(@patterns, $child);
157 7 100       17 }
158 5         7 $attributes{$attr} = \@patterns;
159             } else {
160 7         15 # warn "- $attr: $p\n";
161             my ($childchanged, $child) = $self->rewrite($p, $thunk, $seen, $tree);
162 6         32 $attributes{$attr} = $child;
163             if ($childchanged) {
164             $changed = 1;
165 22         81 }
166 22         45 }
167 22 100       50 }
168 6         14 }
169             if ($changed) {
170             my $class = ref($tree);
171             $rewritten = $class->new( %attributes, children => \@children );
172             # (undef, $rewritten) = $self->rewrite($rewritten, $thunk, $seen, $parent);
173 32 100       85 return (1, $rewritten);
174 16         21 }
175 16         260 }
176             return (0, $tree);
177 16         882 }
178             }
179              
180 16         41 1;
181              
182              
183             =back
184              
185             =head1 BUGS
186              
187             Please report any bugs or feature requests to through the GitHub web interface
188             at L<https://github.com/kasei/attean/issues>.
189              
190             =head1 SEE ALSO
191              
192              
193              
194             =head1 AUTHOR
195              
196             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
197              
198             =head1 COPYRIGHT
199              
200             Copyright (c) 2014--2022 Gregory Todd Williams.
201             This program is free software; you can redistribute it and/or modify it under
202             the same terms as Perl itself.
203              
204             =cut