File Coverage

blib/lib/CodeGen/Cpppp.pm
Criterion Covered Total %
statement 316 347 91.0
branch 89 138 64.4
condition 48 78 61.5
subroutine 36 37 97.3
pod 14 14 100.0
total 503 614 81.9


line stmt bran cond sub pod time code
1             package CodeGen::Cpppp;
2 18     18   2084357 use v5.20;
  18         99  
3 18     18   160 use warnings;
  18         33  
  18         975  
4 18     18   144 use Carp;
  18         37  
  18         1339  
5 18     18   4696 use experimental 'signatures', 'lexical_subs', 'postderef';
  18         38277  
  18         157  
6 18     18   3688 use version;
  18         38  
  18         70  
7 18     18   1545 use Cwd 'abs_path';
  18         47  
  18         1176  
8 18     18   105 use Scalar::Util 'blessed', 'looks_like_number';
  18         52  
  18         1695  
9 18     18   12448 use CodeGen::Cpppp::Template;
  18         72  
  18         147  
10 18     18   109 use CodeGen::Cpppp::Output;
  18         38  
  17         170867  
11              
12             our $VERSION= '0.005'; # VERSION
13             # ABSTRACT: The C Perl-Powered Pre-Processor
14              
15             # These can be inspected by code generators to find out the current
16             # context the code is being inserted into. They are localized by
17             # the template engine.
18             our $CURRENT_INDENT_PREFIX= '';
19             our $CURRENT_IS_INLINE= 0;
20             our $INDENT= ' ';
21              
22              
23 33     34 1 56 sub autoindent($self, $newval=undef) {
  33         57  
  33         71  
  33         67  
24 33 50       115 $self->{autoindent}= $newval if defined $newval;
25 33   50     247 $self->{autoindent} // 1;
26             }
27 33     33 1 60 sub autocolumn($self, $newval=undef) {
  33         82  
  33         57  
  33         49  
28 33 50       127 $self->{autocolumn}= $newval if defined $newval;
29 33   50     387 $self->{autocolumn} // 1;
30             }
31              
32 15     15 1 30 sub convert_linecomment_to_c89($self, $newval=undef) {
  15         22  
  15         36  
  15         19  
33 15 50       97 $self->{convert_linecomment_to_c89}= $newval if defined $newval;
34 15   100     123 $self->{convert_linecomment_to_c89} // 0;
35             }
36              
37              
38 1   50 1 1 5 sub include_path { $_[0]{include_path} //= [] }
39 42   66 42 1 328 sub output { $_[0]{output} //= CodeGen::Cpppp::Output->new }
40              
41              
42 17     17 1 1925093 sub new($class, @attrs) {
  17         50  
  17         44  
  17         30  
43             my $self= bless {
44 17 50 33     163 @attrs == 1 && ref $attrs[0]? %{$attrs[0]}
  0 50       0  
45             : !(@attrs&1)? @attrs
46             : croak "Expected even-length list or hashref"
47             }, $class;
48             $self->{include_path}= [ $self->{include_path} ]
49 17 100 66     233 if defined $self->{include_path} && ref $self->{include_path} ne 'ARRAY';
50 17         77 $self;
51             }
52              
53              
54 3     3 1 3 sub require_template($self, $filename) {
  3         3  
  3         4  
  3         4  
55 3   66     9 $self->{templates}{$filename} ||= do {
56 1 50       4 my $path= $self->find_template($filename)
57             or croak("No template '$filename' found");
58 1   33     20 $self->{templates}{$path} ||= $self->compile_cpppp($path);
59             }
60             }
61              
62              
63 1     1 1 1 sub find_template($self, $filename) {
  1         1  
  1         2  
  1         2  
64 1 50 33     3 return abs_path($filename) if $filename =~ m,/, and -e $filename;
65             # /foo ./foo and ../foo do not trigger a path search
66 1 50       3 return undef if $filename =~ m,^\.?\.?/,;
67 1         3 for ($self->include_path->@*) {
68 1         2 my $p= "$_/$filename";
69 1         3 $p =~ s,//,/,g; # in case include-path ends with '/'
70 1 50       72 return abs_path($p) if -e $p;
71             }
72 0         0 return undef;
73             }
74              
75              
76 12     12 1 27 sub new_template($self, $class_or_filename, @params) {
  12         37  
  12         18  
  12         24  
  12         19  
77 12 100 66     146 my $class= $class_or_filename =~ /^CodeGen::Cpppp::/ && $class_or_filename->can('new')
78             ? $class_or_filename
79             : $self->require_template($class_or_filename);
80             my %params= (
81             context => $self,
82             output => $self->output,
83             !(@params&1)? @params
84 12 50 33     80 : 1 == @params && ref $params[0] eq 'HASH'? %{$params[0]}
  8 100       36  
85             : croak("Expected even-length key/val list, or hashref"),
86             );
87 12         63 $class->new(\%params);
88             }
89              
90              
91             our $next_pkg= 1;
92 24     24 1 39823 sub compile_cpppp($self, @input_args) {
  24         49  
  24         58  
  24         37  
93 24         236 my $parse= $self->parse_cpppp(@input_args);
94 24         100 my $perl= $self->_gen_perl_template_package($parse);
95 24 50   13   3571 unless (eval $perl) {
  13     13   225  
  13     4   42  
  13     4   74  
  13     2   21  
  13     2   107  
  4         62  
  4         231  
  4         20  
  4         12  
  4         20  
  2         30  
  2         6  
  2         7  
  2         4  
  2         10  
96 0         0 die "$perl\n\nException: $@\n";
97             }
98 24         547 return $parse->{package};
99             }
100              
101 24     24   124 sub _gen_perl_template_package($self, $parse, %opts) {
  24         36  
  24         41  
  24         34  
  24         34  
102 24   50     77 my $perl= $parse->{code} // '';
103 24         69 my ($src_lineno, $src_filename, @global, $perl_ver, $cpppp_ver, $tpl_use_line)= (1);
104             # Extract all initial 'use' and 'no' statements from the script.
105             # If they refer to perl or CodeGen:::Cpppp, make a note of it.
106 24         224 while ($perl =~ s/^ ( [ \t]+ | [#] .* | use [^;]+ ; | no [^;]+ ; \s* ) \n//gx) {
107 23         100 my $line= $1;
108 23         47 push @global, $line;
109 23 50       99 $perl_ver= version->parse($1)
110             if $line =~ /use \s+ ( v.* | ["']? [0-9.]+ ["']? ) \s* ; /x;
111 23 50       98 $cpppp_ver= version->parse($1)
112             if $line =~ /use \s+ CodeGen::Cpppp \s* ( v.* | ["']? [0-9.]+ ["']? ) \s* ; /x;
113 23 50       82 $tpl_use_line= 1
114             if $line =~ /use \s+ CodeGen::Cpppp::Template \s+/;
115 23 100       122 if ($line =~ /^# line (\d+) "([^"]+)"/) {
116 22         58 $src_lineno= $1;
117 22         102 $src_filename= $2;
118             } else {
119 1         7 $src_lineno+= 1 + (()= $line =~ /\n/g);
120             }
121             }
122 24 50       130 if ($opts{with_data}) {
123 0         0 require Data::Dumper;
124 0         0 my $dumper= Data::Dumper->new([ { %$parse, code => '...' } ], [ '$_parse_data' ])
125             ->Indent(1)->Sortkeys(1);
126 0         0 push @global,
127             'our $_parse_data; '.$dumper->Dump;
128             }
129              
130             # Build the boilerplate for the template eval
131 24         368 my $pkg= CodeGen::Cpppp::Template->_create_derived_package($cpppp_ver, $parse);
132 24         90 $parse->{package}= $pkg;
133 24   33     160 $cpppp_ver //= $VERSION;
134 24   66     63 $src_filename //= $parse->{filename};
135 24         270 join '', map "$_\n",
136             "package $pkg;",
137             # Inject a minimum perl version unless user-provided
138             ("use v5.20;")x!(defined $perl_ver),
139             # Inject a Template -setup unless user-provided
140             ("use CodeGen::Cpppp::Template -setup => $cpppp_ver;")x!($tpl_use_line),
141             # All the rest of the user's use/no statements
142             @global,
143             # Everything after that goes into a BUILD method
144             $pkg->_gen_BUILD_method($cpppp_ver, $perl, $src_filename, $src_lineno),
145             "1";
146             }
147              
148             sub parse_cpppp($self, $in, $filename=undef, $line=undef) {
149             my @lines;
150             if (ref $in eq 'SCALAR') {
151             @lines= split /^/m, $$in;
152             }
153             else {
154             my $fh;
155             if (ref $in eq 'GLOB' || (blessed($in) && $in->can('getline'))) {
156             $fh= $in;
157             } else {
158             open($fh, '<', $in) or croak "open($in): $!";
159             }
160             local $/= undef;
161             my $text= <$fh>;
162             $filename //= "$in";
163             utf8::decode($text) or warn "$filename is not encoded as utf-8\n";
164             @lines= split /^/m, $text;
165             }
166             $line //= 1;
167             $self->{cpppp_parse}= {
168             autoindent => $self->autoindent,
169             autocolumn => $self->autocolumn,
170             filename => $filename,
171             colmarker => {},
172             coltrack => { },
173             };
174             my ($perl, $perl_line, $block_group, $tpl_start_line, $cur_tpl, $pod_start, @pod)
175             = ('', 0, 1);
176             my sub end_tpl {
177             if (defined $cur_tpl && $cur_tpl =~ /\S/) {
178             my $parsed= $self->_parse_code_block($cur_tpl, $filename, $tpl_start_line);
179             my $current_indent= $perl =~ /\n([ \t]*).*\n\Z/? $1 : '';
180             $current_indent .= ' ' if $perl =~ /\{ *\n\Z/;
181             $perl .= $self->_gen_perl_call_code_block($parsed, $current_indent);
182             }
183             $cur_tpl= undef;
184             };
185             for (@lines) {
186             if (/^=(\w+)/) {
187             if (!defined $pod_start) {
188             $pod_start= $line;
189             if (defined $cur_tpl) {
190             # trim off any blank line that occurs right before the pod.
191             chomp $cur_tpl;
192             end_tpl();
193             }
194             }
195             push @pod, $_;
196             if (@pod > 1 && $1 eq 'cut') {
197             my $current_indent= $perl =~ /\n([ \t]*).*\n\Z/? $1 : '';
198             $current_indent .= ' ' if $perl =~ /\{ *\n\Z/;
199             $perl .= $self->_gen_perl_emit_pod_block(join('', @pod), $filename, $pod_start, $current_indent);
200             @pod= ();
201             $pod_start= undef;
202             }
203             }
204             elsif (defined $pod_start) {
205             push @pod, $_;
206             }
207             elsif (/^#!/) { # ignore #!
208             }
209             elsif (/^##/) { # full-line of perl code
210             end_tpl() if defined $cur_tpl;
211             $perl .= qq{# line $line "$filename"\n} unless $perl_line == $line;
212             (my $pl= $_) =~ s/^##\s?//;
213             $perl .= $self->_transform_template_perl($pl, $line);
214             $perl_line= $line+1;
215             }
216             elsif (/^(.*?) ## ?((?:if|unless|for|while|unless) .*)/) { # perl conditional suffix, half tpl/half perl
217             my ($tpl, $pl)= ($1, $2);
218             end_tpl() if defined $cur_tpl;
219             $tpl_start_line= $line;
220             $cur_tpl= $tpl;
221             end_tpl();
222             $perl =~ s/;\s*$//; # remove semicolon
223             $pl .= ';' unless $pl =~ /;\s*$/; # re-add it if user didn't
224             $perl .= qq{\n# line $line "$filename"\n $pl\n};
225             $perl_line= $line + 1;
226             }
227             else { # default is to assume a line of template
228             if (!defined $cur_tpl) {
229             $tpl_start_line= $line;
230             $cur_tpl= '';
231             }
232             $cur_tpl .= $_;
233             }
234             } continue { ++$line }
235             end_tpl() if defined $cur_tpl;
236              
237             # Resolve final bits of column tracking
238             my $ct= delete $self->{cpppp_parse}{coltrack};
239             _finish_coltrack($ct, $_) for grep looks_like_number($_), keys %$ct;
240              
241             # Finish detecting indent, if not specified
242             if (!defined $self->{cpppp_parse}{indent}) {
243             $self->{cpppp_parse}{indent}
244             = $self->_guess_indent(delete $self->{cpppp_parse}{indent_seen} || []);
245             }
246              
247             $self->{cpppp_parse}{code}= $perl;
248             delete $self->{cpppp_parse};
249             }
250              
251 33     33   59 sub _guess_indent($self, $indent_seen) {
  33         54  
  33         56  
  33         66  
252 33         75 my %evidence;
253             my $prev;
254 33         82 for (@$indent_seen) {
255 30 100 100     101 if (!defined $prev || length($_) <= length($prev)) {
    50          
256 23 50       94 $evidence{/^\t+$/? "\t" : /\t/? 'mixed_tabs' : $_}++;
    100          
257             }
258             elsif (length($prev) < length($_)) {
259 7 50 33     37 if ($prev =~ /\t/ || $_ =~ /\t/) {
260 0 0 0     0 if ($prev =~ /^\t+$/ && $_ =~ /^\t+$/) {
261 0         0 $evidence{"\t"}++;
262             } else {
263 0         0 $evidence{mixed_tabs}++;
264             }
265             } else {
266 7         11 my $step= length($_) - length($prev);
267 7 100       14 if (0 == length($prev) % $step) {
268 5         13 $evidence{' 'x$step}++;
269             }
270             }
271             }
272 30         51 $prev= $_;
273             }
274 33         57 my $guess;
275 33         120 for (keys %evidence) {
276             $guess= $_ if !defined $guess
277             || $evidence{$_} > $evidence{$guess}
278 12 50 66     53 || ($evidence{$_} == $evidence{$guess} && $_ lt $guess);
      33        
      66        
279             }
280 33 50 66     294 return defined $guess && $guess eq 'mixed_tabs'? undef : $guess;
281             }
282              
283 80     80   100 sub _transform_template_perl($self, $pl, $line) {
  80         106  
  80         104  
  80         112  
  80         95  
284             # If user declares "sub NAME(", convert that to "my sub NAME" so that it can
285             # capture refs to the variables of new template instances.
286 80 100       249 if ($pl =~ /^ \s* (my \s+)? sub \s* ([\w_]+) \b \s* /x) {
287 5         20 my $name= $2;
288 5         33 $self->{cpppp_parse}{template_method}{$name}= { line => $line };
289 5         21 my $ofs= $-[0];
290 5 50       33 my $ofs2= defined $1? $+[1] : $ofs;
291 5         32 substr($pl, $ofs, $ofs2-$ofs, "my sub $name; \$self->define_template_method($name => \\&$name);");
292             }
293             # If user declares 'param $foo = $x' adjust that to 'param my $foo = $x'
294 80 100       350 if ($pl =~ /^ \s* (param) \b /xgc) {
    50          
295 15         47 my $ofs= $-[1];
296             # It's an error if the thing following isn't a variable name
297 15 50       63 $pl =~ /\G \s* ( [\$\@\%] [\w_]+ ) /xgc
298             or croak("Expected variable name (including sigil) after 'param'");
299 15         33 my $var_name= $1;
300 15 50       64 $pl =~ /\G \s* ([;=]) /xgc
301             or croak("Parameter declaration $var_name must be followed by '=' or ';'");
302 15         24 my $term= $1;
303 15         37 my $name= substr($var_name, 1);
304 15 100       88 substr($pl, $ofs, $+[0]-$ofs, qq{param '$name', \\my $var_name }.($term eq ';'? ';' : ','));
305 15         65 $self->{cpppp_parse}{template_parameter}{$name}= substr($var_name,0,1);
306             }
307             # If user declares "define name(", convert that to both a method and a define
308             elsif ($pl =~ /^ \s* (define) \s+ ([\w_]+) (\s*) \(/x) {
309 0         0 my $name= $2;
310 0         0 $self->{cpppp_parse}{template_macro}{$name}= 'CODE';
311 0         0 substr($pl, $-[1], $-[2]-$-[1], qq{my sub $name; \$self->define_template_macro($name => \\&$name); sub });
312             }
313 80         238 $pl;
314             }
315              
316 43     43   77 sub _gen_perl_call_code_block($self, $parsed, $indent='') {
  43         80  
  43         57  
  43         87  
  43         84  
317 43   100     273 my $codeblocks= $self->{cpppp_parse}{code_block_templates} ||= [];
318 43         92 push @$codeblocks, $parsed;
319 43         137 my $code= $indent.'$self->_render_code_block('.$#$codeblocks;
320 43         75 my %cache;
321 43         103 my $i= 0;
322 43         72 my $cur_line= 0;
323 43         69 for my $s (@{$parsed->{subst}}) {
  43         103  
324 79 100       140 if (defined $s->{eval}) {
325             # No need to create more than one anonsub for the same expression
326 66 100       143 if (defined $cache{$s->{eval}}) {
327 19         46 $s->{eval_idx}= $cache{$s->{eval}};
328 19         28 next;
329             }
330 47         211 $cache{$s->{eval}}= $s->{eval_idx}= $i++;
331 47 50       184 my $sig= $s->{eval} =~ /self|output/? '($self, $output)' : '';
332 47 100       124 if ($s->{line} == $cur_line) {
    100          
333 11         36 $code .= qq{, sub${sig}{ $s->{eval} }};
334             } elsif ($s->{line} == $cur_line+1) {
335 6         7 $cur_line++;
336 6         16 $code .= qq{,\n$indent sub${sig}{ $s->{eval} }};
337             } else {
338 30         128 $code .= qq{,\n# line $s->{line} "$parsed->{file}"\n$indent sub${sig}{ $s->{eval} }};
339 30         93 $cur_line= $s->{line};
340 30         238 $cur_line++ for $s->{eval} =~ /\n/g;
341             }
342             }
343             }
344 43 100       166 $code .= "\n$indent" if index($code, "\n") >= 0;
345 43         205 $code . ");\n";
346             }
347              
348 2     2   3 sub _gen_perl_emit_pod_block($self, $pod, $file, $line, $indent='') {
  2         4  
  2         3  
  2         4  
  2         5  
  2         5  
  2         29  
349 2   100     16 my $pod_blocks= $self->{cpppp_parse}{pod_blocks} ||= [];
350 2         12 push @$pod_blocks, { pod => $pod, file => $file, line => $line };
351 2         14 return $indent.'$self->_render_pod_block('.$#$pod_blocks.");\n";
352             }
353              
354 4     4   6 sub _finish_coltrack($coltrack, $col) {
  4         23  
  4         4  
  4         7  
355             # did it eventually have an eval to the left?
356 4 50       18 if (grep $_->{follows_eval}, $coltrack->{$col}{members}->@*) {
357 4         15 $coltrack->{$col}{members}[-1]{last}= 1;
358             } else {
359             # invalidate them all, they won't become unaligned anyway.
360 0         0 $_->{colgroup}= undef for $coltrack->{$col}{members}->@*;
361             }
362 4         14 delete $coltrack->{$col};
363             }
364              
365 45     45   11487 sub _parse_code_block($self, $text, $file=undef, $orig_line=undef) {
  45         62  
  45         75  
  45         77  
  45         69  
  45         81  
366 45 100       223 $text .= "\n" unless substr($text,-1) eq "\n";
367 45 50       148 if ($text =~ /^# line (\d+) "([^"]+)"/) {
368 0         0 $orig_line= $1-1;
369 0         0 $file= $2;
370             }
371 45   100     127 local our $line= $orig_line || 1;
372 45   100     161 local our $parse= $self->{cpppp_parse} //= {};
373 45         68 local our $start;
374 45         121 local our @subst;
375             # Check if we can auto-detect the indent
376 45 50       179 unless (defined $parse->{indent}) {
377             # Find all total indents used in this code, but only count lines that
378             # were preceeded by ';' or '{' or ')' followed by lines starting with a
379             # word or variable substitution.
380 45         289 push @{$parse->{indent_seen}}, $1 while $text =~ /[;{)]\s*\n([ \t]+)[\w\$\@]/g;
  31         196  
381             }
382             # Everything in coltrack that survived the last _parse_code_block call
383             # ended on the final line of the template. Set the line numbers to
384             # continue into this template.
385 45         200 for my $c (grep looks_like_number($_), keys $parse->{coltrack}->%*) {
386 6         9 $parse->{coltrack}{$c}{line}= $line;
387             }
388 45         107 local $_= $text;
389             # Parse and record the locations of the embedded perl statements
390 45         218 ()= m{
391             # Rough approximation of continuation of perl expressions in quoted strings
392             (?(DEFINE)
393             (? (?>
394             \{ (?&BALANCED_EXPR) \}
395             | \[ (?&BALANCED_EXPR) \]
396             | \( (?&BALANCED_EXPR) \)
397             | [^[\](){}\n]+
398 0         0 | \n (?{ $line++ })
399             )* )
400             )
401            
402             # Start of a perl expression in a quoted string
403 69         276 [\$\@] (?{ $start= -1+pos })
404             (?:
405             \{ (?&BALANCED_EXPR) \} #
406             | [\w_]+ # plain variable
407             (?: # maybe followed by ->[] or similar
408             (?: -> )?
409             (?: \{ (?&BALANCED_EXPR) \} | \[ (?&BALANCED_EXPR) \] )
410             ) *
411 69         389 ) (?{ push @subst, { pos => $start, len => -$start+pos, line => $line }; })
412            
413             # Track what line we're on
414 146         388 | \n (?{ $line++ })
415            
416             # Column alignment detection for the autocolumn feature
417 1879         3181 | (?{ $start= pos; }) [ \t]{2,}+ (?{
418 72         355 push @subst, { pos => pos, len => 0, line => $line, colgroup => undef };
419             })
420             }xg;
421            
422 45         91 my $prev_eval;
423 45         109 for my $s (@subst) {
424 141 100       272 if (exists $s->{colgroup}) {
425 72         169 my $linestart= (rindex($text, "\n", $s->{pos})+1);
426 72         106 my $col= $s->{pos} - $linestart;
427 72   100     197 $s->{follows_eval}= $prev_eval && $prev_eval->{line} == $s->{line};
428             # If same column as previous line, continue the coltracking.
429 72 100       150 if ($parse->{coltrack}{$col}) {
430 10 50       26 if ($parse->{coltrack}{$col}{members}[-1]{line} == $s->{line} - 1) {
431 10         14 push @{ $parse->{coltrack}{$col}{members} }, $s;
  10         24  
432 10         17 $s->{colgroup}= $parse->{coltrack}{$col}{id};
433 10         16 $parse->{coltrack}{$col}{line}= $s->{line};
434 10         19 next;
435             }
436             # column ended prior to this
437 0         0 _finish_coltrack($parse->{coltrack}, $col);
438             }
439             # There's no need to create a column unless nonspace to the left
440             # Otherwise it would just be normal indent.
441 62 100       189 if (substr($text, $linestart, $s->{pos} - $linestart) =~ /\S/) {
442             # new column begins
443 5         22 $s->{colgroup}= $col*10000 + ++$parse->{coltrack}{next_id}{$col};
444 5         11 $s->{first}= 1;
445             $parse->{coltrack}{$col}= {
446             id => $s->{colgroup},
447             line => $s->{line},
448 5         36 members => [ $s ],
449             };
450             }
451             }
452             else { # Perl expression
453 69         172 my $expr= substr($text, $s->{pos}, $s->{len});
454             # Special case: ${{ }} notation is a shortcut for @{[do{ ... }]}
455 69         205 $expr =~ s/^ \$\{\{ (.*) \}\} $/$1/x;
456             # When not inside a string, ${foo} becomes ambiguous with ${foo()}
457 69         220 $expr =~ s/^ ([\$\@]) \{ ([\w_]+) \} /$1$2/x;
458 69         122 $s->{eval}= $expr;
459 69         118 $prev_eval= $s;
460             }
461             }
462             # Clean up any tracked column that ended before the final line of the template
463 45         152 for my $c (grep looks_like_number($_), keys $parse->{coltrack}->%*) {
464             _finish_coltrack($parse->{coltrack}, $c)
465 11 100       53 if $parse->{coltrack}{$c}{line} < $line-1;
466             }
467 45   100     366 @subst= grep defined $_->{eval} || defined $_->{colgroup}, @subst;
468            
469 45         313 { text => $text, subst => \@subst, file => $file }
470             }
471              
472              
473 2     2 1 4 sub patch_file($self, $fname, $patch_markers, $new_content) {
  2         2  
  2         3  
  2         3  
  2         8  
  2         2  
474 2 50 66     12 $new_content .= "\n" unless $new_content =~ /\n\Z/ or !length $new_content;
475 2         8 utf8::encode($new_content);
476 2 50       137 open my $fh, '+<', $fname or die "open($fname): $!";
477 2         4 my $content= do { local $/= undef; <$fh> };
  2         10  
  2         75  
478 2 50       129 $content =~ s{(BEGIN \Q$patch_markers\E[^\n]*\n).*?(^[^\n]+?END \Q$patch_markers\E)}
479             {$1$new_content$2}sm
480             or croak "Can't find $patch_markers in $fname";
481 2 50       153 $fh->seek(0,0) or die "seek: $!";
482 2 50       15628 $fh->print($content) or die "write: $!";
483 2 50       35 $fh->truncate($fh->tell) or die "truncate: $!";
484 2 50       408 $fh->close or die "close: $!";
485 2         49 $self;
486             }
487              
488              
489 6     6 1 15 sub backup_and_overwrite_file($self, $fname, $new_content) {
  6         11  
  6         14  
  6         11  
  6         10  
490 6 50       126 $new_content .= "\n" unless $new_content =~ /\n\Z/;
491 6         33 utf8::encode($new_content);
492 6 50       305 if (-e $fname) {
493 0         0 my $n= 0;
494 0         0 ++$n while -e "$fname.$n";
495 0         0 require File::Copy;
496 0 0       0 File::Copy::copy($fname, "$fname.$n") or die "copy($fname, $fname.$n): $!";
497             }
498 6 50       1466 open my $fh, '>', $fname or die "open($fname): $!";
499 6 50       586 $fh->print($new_content) or die "write: $!";
500 6 50       51629 $fh->close or die "close: $!";
501 6         493 $self;
502             }
503              
504              
505 15     15 1 31 sub get_filtered_output($self, @sections) {
  15         28  
  15         44  
  15         23  
506 15         58 @sections= grep defined, @sections; # allow a single undef to mean 'all'
507 15         46 my $content= $self->output->get(@sections);
508 15 100       67 if ($self->convert_linecomment_to_c89) {
509             # rewrite '//' comments as '/*' comments
510 4         1531 require CodeGen::Cpppp::CParser;
511 4         37 my @tokens= CodeGen::Cpppp::CParser->tokenize($content);
512 4         10 my $ofs= 0;
513 4         13 for (@tokens) {
514 20         46 $_->[2] += $ofs;
515 20 100       47 if ($_->type eq 'comment') {
516 6 100       18 if (substr($content, $_->src_pos, 2) eq '//') {
517 3         9 substr($content, $_->src_pos, $_->src_len, '/*'.$_->value.' */');
518 3         26 $ofs += 3;
519             }
520             }
521             }
522             }
523 15         63 $content;
524             }
525              
526              
527 8     8 1 25 sub write_sections_to_file($self, $sections, $fname, $patch_markers=undef) {
  8         15  
  8         20  
  8         42  
  8         19  
  8         20  
528 8         43 my $content= $self->get_filtered_output($sections);
529 8 100       27 if (defined $patch_markers) {
530 2         10 $self->patch_file($fname, $patch_markers, $content);
531             } else {
532 6         29 $self->backup_and_overwrite_file($fname, $content);
533             }
534 8         38 $self
535             }
536              
537 0     0   0 sub _slurp_file($self, $fname) {
  0         0  
  0         0  
  0         0  
538 0 0       0 open my $fh, '<', $fname or die "open($fname): $!";
539 0         0 my $content= do { local $/= undef; <$fh> };
  0         0  
  0         0  
540 0 0       0 $fh->close or die "close: $!";
541 0         0 $content;
542             }
543              
544             1;
545              
546             __END__