File Coverage

blib/lib/Acme/Pythonic.pm
Criterion Covered Total %
statement 90 90 100.0
branch 55 56 98.2
condition 21 21 100.0
subroutine 11 11 100.0
pod 0 6 0.0
total 177 184 96.2


line stmt bran cond sub pod time code
1             package Acme::Pythonic;
2              
3             # Please, if you tested it in some earlier version of Perl and works let
4             # me know! The versions of Filter::Simple, Text::Tabs, and Test::More
5             # would be useful as well.
6 23     23   637030 use 5.006_001;
  23         94  
  23         1219  
7 23     23   147 use strict;
  23         45  
  23         881  
8 23     23   118 use warnings;
  23         44  
  23         1709  
9              
10             our ($VERSION, $DEBUG, $CALLER);
11             $VERSION = '0.47';
12              
13 23     23   22535 use Text::Tabs;
  23         21070  
  23         4017  
14              
15             sub import {
16             my ($package, %cfg) = @_;
17             $DEBUG = $cfg{debug};
18             $CALLER = caller() # to be able to check sub prototypes
19             }
20              
21              
22 23     23   30304 use Filter::Simple;
  23         829058  
  23         186  
23             FILTER_ONLY code => sub {
24             unpythonize();
25             cuddle_elses_and_friends();
26             if ($DEBUG) {
27             s/$Filter::Simple::placeholder/BLANKED_OUT/g;
28             print;
29             $_ = '1;';
30             }
31             };
32              
33              
34             # This regexp matches a 7-bit ASCII identifier. We use atomic grouping
35             # because an identifier cannot be backtracked.
36             my $id = qr/(?>[_a-zA-Z](?:[_a-zA-Z0-9']|::)*)/;
37              
38             # Shorthand to put an eventual trailing comment in some regexps.
39             my $tc = qr/(?
40              
41              
42             # Tries its best at converting Pythonic code to Perl.
43             sub unpythonize {
44             # Sometimes Filter::Simple adds newlines blanking out stuff, which
45             # interferes with Pythonic conventions.
46 22     22 0 99 my %bos = (); # BlanketOutS
47 22         51 my $count = 0;
48 22         587 s<$Filter::Simple::placeholder>
  146         353  
49            
50 146         499 $bos{$bo} = $&;
51 146         577 $bo>geo;
52              
53             # In addition, we can now normalize newlines without breaking
54             # Filter::Simple's identifiers.
55 22         103 normalize_newlines();
56 22         733 my @lines = split /\n/;
57 22 50       236 return unless @lines;
58              
59             # If unsure about the ending indentation level, add an extra
60             # non-indented line to ensure the stack gets emptied.
61 22 100       148 push @lines, '1; # added by Acme::Pythonic' if $lines[-1] =~ /^(?:\s|\s*#)/;
62              
63 22         49 my ($comment, # comment in the current line, if any
64             $indent, # indentation of the current logical line
65             $id_at_sob, # identifier at StartOfBlock, for instance "else", or "eval"
66             $prev_line_with_code, # previous line with code
67             $might_be_modifier, # flag: current logical line might be a modifier
68             $line_with_modifier, # physical line which started the current modifier
69             $joining, # flag: are we joining lines?
70             $unbalanced_paren, # flag: we opened a paren that remains to be closed
71             @stack, # keeps track of indentation stuff
72             );
73              
74 22         62 @stack = ();
75 22         66 foreach my $line (@lines) {
76             # We remove any trailing comment so that we can assert stuff
77             # easily about the end of the code in this line. It is later
78             # appended back in the continue block below.
79 2046 100       5823 $comment = $line =~ s/(\s*$tc)//o ? $1 : '';
80 2046 100       6438 next if $line =~ /^\s*$/;
81              
82 1306 100       2438 if (!$joining) {
83 1231         2192 $unbalanced_paren = left_parenthesize($line);
84 1231         3099 $might_be_modifier = $line =~ /^\s*(?:if|unless|while|until|for|foreach)\b/;
85 1231 100       2435 $line_with_modifier = \$line if $might_be_modifier;
86 1231         3907 ($indent) = $line =~ /^(\s*)/;
87 1231         3645 $indent = length(expand($indent));
88             }
89              
90 1306 100 100     24547 if ($line =~ /(?:,|=>)\s*$/ || $line =~ s/\\\s*$//) {
91 75         82 ++$joining;
92 75 100       189 next if $joining > 1; # if 1 we need yet to handle indentation
93             } else {
94 1231         1593 $joining = 0;
95             }
96              
97             # Handle trailing colons, which can be Pythonic, mark a labeled
98             # block, mean some map, or &-sub call, etc.
99             #
100             # We check the parity of the number of ending colons to try to
101             # avoid breaking things like
102             #
103             # print for keys %main::
104             #
105 1262         1818 my $bracket_opened_by = '';
106 1262 100 100     11584 if ($line =~ /(:+)$/ && length($1) % 2) {
    100          
107 272         330 $might_be_modifier = 0;
108             # We perform some checks because labels have to keep their colon.
109 272 100 100     2551 if ($line !~ /^\s*$id:$/o ||
      100        
110             $line =~ /[[:lower:]]/ || # labels are not allowed to have lower-case letters
111             $line =~ /^\s*(?:BEGIN|CHECK|INIT|END):$/) {
112 264         475 chop $line;
113 264 100       522 if ($unbalanced_paren) {
114 165         237 $line .= ")";
115 165         308 $unbalanced_paren = 0;
116             } else {
117 99         1466 ($bracket_opened_by) = $line =~ /($id)\s*$/o;
118             }
119             }
120             } elsif (!$joining) {
121 959 100       1673 $$line_with_modifier =~ s/\(// if $might_be_modifier;
122 959         1598 $unbalanced_paren = 0;
123 959         1717 $line .= ';';
124             }
125              
126             # Handle indentation. Language::Pythonesque was the basis of
127             # this code.
128 1262 100       2831 my $prev_indent = @stack ? $stack[-1]{indent} : 0;
129 1262 100       3816 if ($prev_indent < $indent) {
    100          
130 269         1245 push @stack, {indent => $indent, id_at_sob => $id_at_sob};
131 269 100       2007 $$prev_line_with_code .= " {" unless $$prev_line_with_code =~ s/(?=\s*$tc)/ {/o;
132             } elsif ($prev_indent > $indent) {
133 232         790 do {
134 269         430 my $prev_id_at_sob = $stack[-1]{id_at_sob};
135 269         300 pop @stack;
136 269 100       756 $prev_indent = @stack ? $stack[-1]{indent} : 0;
137 269         706 $$prev_line_with_code .= "\n" . ((' ' x $prev_indent) . "}");
138 269 100       486 $$prev_line_with_code .= ";" if needs_semicolon($prev_id_at_sob);
139             } while $prev_indent > $indent;
140 232 100       487 $$prev_line_with_code =~ s/;$/ / if $might_be_modifier;
141             }
142 1262         2266 $id_at_sob = $bracket_opened_by;
143             } continue {
144 2046         2709 $line =~ s/^\s*pass;?\s*$//;
145 2046 100 100     10917 $prev_line_with_code = \$line if !$joining && $line =~ /\S/;
146 2046         4288 $line .= $comment;
147             }
148              
149 22         488 $_ = join "\n", @lines;
150 22         1104 s/$;BLANKED_OUT_\d+$;/$bos{$&}/go;
151             }
152              
153              
154             # In the trials I've done seems like the Python interpreter understands
155             # any of the three conventions, even if they are not the ones in the
156             # platform, and even if they are mixed in the same file.
157             #
158             # In addition, it guarantees make test works no matter the platform.
159             sub normalize_newlines {
160 22     22 0 126 s/\015\012/\n/g;
161 22         172 tr/\015/\n/ unless "\n" eq "\015";
162 22         49 tr/\012/\n/ unless "\n" eq "\012";
163             }
164              
165              
166             # Put an opening paren in the places we forgive parens. It will be later
167             # closed or removed as needed in the main subroutine.
168             sub left_parenthesize {
169 1231 100 100 1231 0 16991 $_[0] =~ s/^(\s*\b(?:if|elsif|unless)\b\s*)/$1(/ ||
170 44 100       366 $_[0] =~ s/^(\s*(?:$id\s*:)?\s*\b(?:while|until)\b(\s*))/$2 eq '' ? "$1 (" : "$1("/eo ||
171 69         166 $_[0] =~ s/^(\s*(?:$id\s*:\s*)?\bfor(?:each)?\b\s*)(.*)/fortype_guesser($1,$2)/oxe
172             }
173              
174              
175             # Tries its best at guessing a for(each) type or, at least, where to put
176             # the opening paren.
177             #
178             # Returns a string which is a copy of the original with the paren
179             # inserted.
180             sub fortype_guesser {
181 69     69 0 235 my ($for, $rest) = @_;
182 69         88 my $guess = "";
183              
184             # Try to match "for VAR in LIST", and "for VAR LIST"
185 69 100 100     1481 if ($rest =~ m/^((?:my|our)? \s* \$ $id\s+) in\s* ((?: (?:[\$\@%&\\]) | (?:\b\w) ) .*)$/ox ||
186             $rest =~ m/^((?:my|our)? \s* \$ $id\s*) ((?: (?:[\$\@%&\\]) | (?:\b\w) ) .*)$/ox) {
187 29         134 $guess = "$for$1($2";
188             } else {
189             # We are not sure whether this is a for or a foreach, but it is
190             # very likely that putting parens around gets it right.
191 40         62 $rest =~ s/^\s*in\b//; # fixes "foreach in LIST"
192 40         88 $guess = "$for($rest";
193             }
194              
195 69         350 return $guess;
196             }
197              
198              
199             # Guesses whether a block started by $id_at_sob needs a semicolon after the
200             # ending bracket.
201             sub needs_semicolon {
202 269     269 0 350 my $id_at_sob = shift;
203 269 100       1022 return 0 if !$id_at_sob;
204 91 100       413 return 1 if $id_at_sob =~ /^(do|sub|eval)$/;
205              
206 68 100       366 my $proto = $id_at_sob =~ /::/ ? prototype($id_at_sob) : prototype("${CALLER}::$id_at_sob");
207 68 100       387 return 0 if not defined $proto;
208 6         61 return $proto =~ /^;?&$/;
209             }
210              
211              
212             # We follow perlstyle here, as we did until now.
213             sub cuddle_elses_and_friends {
214 22     22 0 1406 s/^([ \t]*})\s*(?=(?:elsif|else|continue)\b)/$1 /gm;
215 22         2075 s/^([ \t]*})\s*(?=(?:if|unless|while|until|for|foreach)\b(?!.*{$tc?$))/$1 /gm;
216             }
217              
218             1;
219              
220              
221             __END__