File Coverage

blib/lib/PerlX/MethodCallWithBlock.pm
Criterion Covered Total %
statement 81 82 98.7
branch 21 26 80.7
condition 5 6 83.3
subroutine 12 12 100.0
pod 0 2 0.0
total 119 128 92.9


line stmt bran cond sub pod time code
1             package PerlX::MethodCallWithBlock;
2 10     10   15204 use strict;
  10         18  
  10         468  
3 10     10   48 use warnings;
  10         16  
  10         247  
4 10     10   214 use 5.008001;
  10         33  
  10         587  
5             our $VERSION = '0.05';
6              
7 10     10   9513 use Devel::Declare ();
  10         81101  
  10         326  
8 10     10   9818 use B::Hooks::EndOfScope ();
  10         169633  
  10         239  
9 10     10   8888 use B::OPCheck ();
  10         133427  
  10         330  
10              
11 10     10   18289 use PPI;
  10         1989635  
  10         410  
12 10     10   108 use PPI::Document;
  10         17  
  10         7871  
13              
14             sub inject_close_paren {
15 12     12 0 1415 my $linestr = Devel::Declare::get_linestr;
16 12         43 my $offset = Devel::Declare::get_linestr_offset;
17 12         34 substr($linestr, $offset, 0) = ')';
18 12         60 Devel::Declare::set_linestr($linestr);
19             }
20              
21             sub checker {
22 645     645 0 135131 my ($op, @args) = @_;
23 645         2158 my $offset = Devel::Declare::get_linestr_offset;
24 645         2306 $offset += Devel::Declare::toke_skipspace($offset);
25 645         1665 my $linestr = Devel::Declare::get_linestr;
26              
27 645 50       1756 return if $offset > length($linestr);
28              
29 645         1150 my $code = substr($linestr, $offset);
30              
31 645         2596 my $doc = PPI::Document->new(\$code);
32 645 50       851257 return unless $doc;
33              
34             # find the structure of "->method(...) {"
35             my $found = $doc->find(
36             sub {
37 6876     6876   124054 my $el = $_[1];
38 6876 100 100     18224 return 0 unless $el->class eq 'PPI::Token::Operator' && $el->content eq '->';
39 138 50       1973 my $word = $el->snext_sibling or return 0;
40 138 100       3693 return 0 unless $word->class eq 'PPI::Token::Word';
41              
42 109 50       768 my $args = $word->snext_sibling or return 0;
43 109 100       2551 if ($args->class eq 'PPI::Structure::List') {
    50          
44 91 100       616 my $block = $args->snext_sibling or return 0;
45 35 100       781 return 0 unless $block->class eq 'PPI::Structure::Block';
46             }
47             elsif ($args->class ne 'PPI::Structure::Block') {
48 0         0 return 0
49             }
50              
51 22         259 return 1;
52             }
53 645         4698 );
54 645 100       14341 return unless $found;
55              
56 22         266 my $injected_code = 'sub { BEGIN { B::Hooks::EndOfScope::on_scope_end(\&PerlX::MethodCallWithBlock::inject_close_paren); }';
57              
58 22         38 my $pnode;
59 22         122 $code = "";
60 22         64 for my $node (@$found) {
61 22         40 $pnode = $node;
62              
63 22         119 while($pnode) {
64 66         373 my $prev_node = $pnode;
65 66         373 while ($prev_node = $prev_node->previous_sibling) {
66 22         603 $code = $prev_node->content . $code;
67             }
68 66         1360 $pnode = $pnode->parent;
69             }
70              
71 22         172 $code .= join "", map { $_->content } ($node, $node->snext_sibling);
  44         555  
72 22         171 my $word = $node->snext_sibling;
73 22 100       399 if ($word->snext_sibling->class eq 'PPI::Structure::Block') {
74 18         433 my $block = $word->snext_sibling;
75 18         411 my @block_elements = $block->elements;
76             # one-line block, we see the whole thing here.
77 18 100 66     290 if ($block_elements[0] eq '{' && $block_elements[-1] eq '}') {
78 10         373 my $block_code = $node->snext_sibling->snext_sibling->content;
79 10         1142 $code .= "(sub $block_code)";
80              
81             # There might be something more after the block...
82 10         64 my $next_node = $block->next_sibling;
83 10         244 while ($next_node) {
84 5         18 $code .= $next_node->content;
85 5         42 $next_node = $next_node->next_sibling;
86             }
87             } else {
88 8         743 $code .= "($injected_code";
89             }
90             }
91             else {
92 4         92 my $args = $word->snext_sibling->content;
93 4         218 $args =~ s/\)$/,$injected_code/;
94 4         14 $code .= $args;
95             }
96 22         197 substr($linestr, $offset) = $code;
97 22         201 Devel::Declare::set_linestr($linestr);
98             }
99             }
100              
101             sub import {
102 10     10   117 my $caller = caller;
103 10         87 my $offset = Devel::Declare::get_linestr_offset();
104 10         45 my $linestr = Devel::Declare::get_linestr();
105              
106 10         41 substr($linestr, $offset, 0) = q[BEGIN { B::OPCheck->import($_ => check => \&PerlX::MethodCallWithBlock::checker) for qw(const pushmark lineseq refgen sassign); }];
107 10         576 Devel::Declare::set_linestr($linestr);
108             }
109              
110             1;
111             __END__
112              
113             =head1 NAME
114              
115             PerlX::MethodCallWithBlock - A Perl extension to allow a bare block after method call
116              
117             =head1 SYNOPSIS
118              
119             use PerlX::MethodCallWithBlock;
120              
121             Foo->bar(1, 2, 3) {
122             say "and a block";
123             };
124              
125             =head1 DESCRIPTION
126              
127             PerlX::MethodCallWithBlock is A Perl extension that extends Perl
128             syntax to allow one bare block follows normal methods calls.
129              
130             It translate:
131              
132             Foo->bar(1, 2, 3) {
133             say "and a block";
134             };
135              
136             Into:
137              
138             Foo->bar(1, 2, 3, sub {
139             say "and a block";
140             });
141              
142             The body of the C<Foo::bar> method sees it as the very last argument.
143              
144             =head1 NOTICE
145              
146             This version is released as a proof that it can be done. However, the
147             internally parsing code for translating codes are very fragile at this
148             moment.
149              
150             It's very possible that your code breaks after you re-indent it. You
151             should send me that piece of code as a failing test if you find such
152             cases.
153              
154             =head1 AUTHOR
155              
156             Kang-min Liu E<lt>gugod@gugod.orgE<gt>
157              
158             =head1 SEE ALSO
159              
160             L<Rubyish>
161              
162             =head1 LICENSE AND COPYRIGHT
163              
164             Copyright (c) 2009, Kang-min Liu C<< <gugod@gugod.org> >>.
165              
166             This is free software, licensed under:
167              
168             The MIT (X11) License
169              
170             =head1 DISCLAIMER OF WARRANTY
171              
172             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
173             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
174             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
175             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
176             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
177             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
178             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
179             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
180             NECESSARY SERVICING, REPAIR, OR CORRECTION.
181              
182             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
183             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
184             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENSE, BE
185             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
186             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
187             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
188             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
189             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
190             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
191             SUCH DAMAGES.
192              
193             =cut