File Coverage

blib/lib/Eval/TypeTiny/CodeAccumulator.pm
Criterion Covered Total %
statement 70 72 97.2
branch 6 10 60.0
condition 5 10 50.0
subroutine 19 19 100.0
pod 13 13 100.0
total 113 124 91.1


line stmt bran cond sub pod time code
1             package Eval::TypeTiny::CodeAccumulator;
2              
3 56     56   1981 use 5.008001;
  56         233  
4 56     56   322 use strict;
  56         147  
  56         1152  
5 56     56   343 use warnings;
  56         136  
  56         2453  
6              
7             BEGIN {
8 56 50   56   2100 if ( $] < 5.010 ) { require Devel::TypeTiny::Perl58Compat }
  0         0  
9             }
10              
11             BEGIN {
12 56     56   257 $Eval::TypeTiny::CodeAccumulator::AUTHORITY = 'cpan:TOBYINK';
13 56         52882 $Eval::TypeTiny::CodeAccumulator::VERSION = '2.003_000';
14             }
15              
16             $Eval::TypeTiny::CodeAccumulator::VERSION =~ tr/_//d;
17              
18             sub new {
19 321     321 1 665 my $class = shift;
20              
21 321 50       1794 my %self = @_ == 1 ? %{$_[0]} : @_;
  0         0  
22 321   50     1824 $self{env} ||= {};
23 321   50     2229 $self{code} ||= [];
24 321   50     1742 $self{placeholders} ||= {};
25 321   50     1515 $self{indent} ||= '';
26              
27 321         1515 bless \%self, $class;
28             }
29              
30 371     371 1 720 sub code { join( "\n", @{ $_[0]{code} } ) }
  371         3930  
31 297     297 1 2779 sub description { $_[0]{description} }
32 297     297 1 1319 sub env { $_[0]{env} }
33              
34             sub add_line {
35 5364     5364 1 8908 my $self = shift;
36 5364         9283 my $indent = $self->{indent};
37              
38 5364         7719 push @{ $self->{code} }, map { $indent . $_ } map { split /\n/ } @_;
  5364         10765  
  7293         20047  
  5364         15151  
39              
40 5364         12662 $self;
41             }
42              
43             sub increase_indent {
44 56     56 1 115 $_[0]{indent} .= "\t";
45 56         100 $_[0];
46             }
47              
48             sub decrease_indent {
49 56     56 1 226 $_[0]{indent} =~ s/\t$//;
50 56         114 $_[0];
51             }
52              
53             sub add_gap {
54 1867     1867 1 2816 push @{ $_[0]{code} }, '';
  1867         5014  
55             }
56              
57             sub add_placeholder {
58 4     4 1 13 my ( $self, $for ) = ( shift, @_ );
59 4   50     17 my $indent = $self->{indent} || '';
60              
61             $self->{placeholders}{$for} = [
62 4         14 scalar( @{ $self->{code} } ),
63             $self->{indent},
64 4         6 ];
65 4         7 push @{ $self->{code} }, "$indent# placeholder [ $for ]";
  4         11  
66              
67 4 100       13 if ( defined wantarray ) {
68 2     1   10 return sub { $self->fill_placeholder( $for, @_ ) };
  1         5  
69             }
70             }
71              
72             sub fill_placeholder {
73 2     2 1 6 my ( $self, $for, @lines ) = ( shift, @_ );
74              
75 2 50       4 my ( $line_number, $indent ) = @{ delete $self->{placeholders}{$for} or die };
  2         10  
76 2         5 my @indented_lines = map { $indent . $_ } map { split /\n/ } @lines;
  2         6  
  2         6  
77 2         4 splice( @{ $self->{code} }, $line_number, 1, @indented_lines );
  2         6  
78              
79 2         6 $self;
80             }
81              
82             sub add_variable {
83 237     237 1 2154 my ( $self, $suggested_name, $reference ) = ( shift, @_ );
84            
85 237         422 my $actual_name = $suggested_name;
86 237         386 my $i = 1;
87 237         735 while ( exists $self->{env}{$actual_name} ) {
88 61         209 $actual_name = sprintf '%s_%d', $suggested_name, ++$i;
89             }
90              
91 237         711 $self->{env}{$actual_name} = $reference;
92              
93 237         635 $actual_name;
94             }
95              
96             sub finalize {
97 297     297 1 547 my $self = shift;
98              
99 297         487 for my $p ( values %{ $self->{placeholders} } ) {
  297         1093  
100 2         3 splice( @{ $self->{code} }, $p->[0], 1 );
  2         6  
101             }
102              
103 297         594 $self;
104             }
105              
106             sub compile {
107 297     297 1 826 my ( $self, %opts ) = ( shift, @_ );
108              
109 297 50       1655 $self->{finalized}++ or $self->finalize();
110              
111 297         2667 require Eval::TypeTiny;
112 297         1043 return Eval::TypeTiny::eval_closure(
113             description => $self->description,
114             %opts,
115             source => $self->code,
116             environment => $self->env,
117             );
118             }
119              
120             1;
121              
122             __END__
123              
124             =pod
125              
126             =encoding utf-8
127              
128             =for stopwords pragmas coderefs
129              
130             =head1 NAME
131              
132             Eval::TypeTiny::CodeAccumulator - alternative API for Eval::TypeTiny
133              
134             =head1 SYNOPSIS
135              
136             my $make_adder = 'Eval::TypeTiny::CodeAccumulator'->new(
137             description => 'adder',
138             );
139            
140             my $n = 40;
141             my $varname = $make_adder->add_variable( '$addend' => \$n );
142            
143             $make_adder->add_line( 'sub {' );
144             $make_adder->increase_indent;
145             $make_adder->add_line( 'my $other_addend = shift;' );
146             $make_adder->add_gap;
147             $make_adder->add_line( 'return ' . $varname . ' + $other_addend;' );
148             $make_adder->decrease_indent;
149             $make_adder->add_line( '}' );
150            
151             my $adder = $make_adder->compile;
152            
153             say $adder->( 2 ); ## ==> 42
154              
155             =head1 STATUS
156            
157             This module is covered by the
158             L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.
159              
160             =head1 DESCRIPTION
161              
162             =head2 Constructor
163              
164             =over
165              
166             =item C<< new( %attrs ) >>
167              
168             The only currently supported attribute is C<description>.
169              
170             =back
171              
172             =head2 Methods
173              
174             =over
175              
176             =item C<< env() >>
177              
178             Returns the current compilation environment, a hashref of variables to close
179             over.
180              
181             =item C<< code() >>
182              
183             Returns the source code so far.
184              
185             =item C<< description() >>
186              
187             Returns the same description given to the constructor, if any.
188              
189             =item C<< add_line( @lines_of_code ) >>
190              
191             Adds the next line of code.
192              
193             =item C<< add_gap() >>
194              
195             Adds a blank line of code.
196              
197             =item C<< increase_indent() >>
198              
199             Increases the indentation level for subsequent lines of code.
200              
201             =item C<< decrease_indent() >>
202              
203             Decreases the indentation level for subsequent lines of code.
204              
205             =item C<< add_variable( $varname, $reference_to_value ) >>
206              
207             Adds a variable to the compilation environment so that the coderef being
208             generated can close over it.
209              
210             If a variable already exists in the environment with that name, will instead
211             add a variable with a different name and return that name. You should always
212             continue to refer to the variable with that returned name, just in case.
213              
214             =item C<< add_placeholder( $placeholder_name ) >>
215              
216             Adds a line of code which is just a comment, but remembers its line number.
217              
218             =item C<< fill_placeholder( $placeholder_name, @lines_of_code ) >>
219              
220             Goes back to a previously inserted placeholder and replaces it with code.
221              
222             As an alternative, C<add_placeholder> returns a coderef, which you can call
223             like C<< $callback->( @lines_of_code ) >>.
224              
225             =item C<< compile( %opts ) >>
226              
227             Compiles the code and returns it as a coderef.
228              
229             Options are passed on to C<< eval_closure >> from L<Eval::TypeTiny>,
230             but cannot include C<code> or C<environment>. C<< alias => 1 >>
231             is probably the option most likely to be useful, but in general
232             you won't need to provide any options.
233              
234             =item C<< finalize() >>
235              
236             This method is called by C<compile> just before compiling the code. All it
237             does is remove unfilled placeholder comments. It is not intended for end
238             users to call, but is documented as it may be a useful hook if you are
239             subclassing this class.
240              
241             =back
242              
243             =head1 BUGS
244              
245             Please report any bugs to
246             L<https://github.com/tobyink/p5-type-tiny/issues>.
247              
248             =head1 SEE ALSO
249              
250             L<Eval::TypeTiny>.
251              
252             =head1 AUTHOR
253              
254             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
255              
256             =head1 COPYRIGHT AND LICENCE
257              
258             This software is copyright (c) 2022-2023 by Toby Inkster.
259              
260             This is free software; you can redistribute it and/or modify it under
261             the same terms as the Perl 5 programming language system itself.
262              
263             =head1 DISCLAIMER OF WARRANTIES
264              
265             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
266             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
267             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.