File Coverage

blib/lib/C/Utility.pm
Criterion Covered Total %
statement 43 166 25.9
branch 3 56 5.3
condition 0 15 0.0
subroutine 12 30 40.0
pod 20 22 90.9
total 78 289 26.9


line stmt bran cond sub pod time code
1             package C::Utility;
2 2     2   137525 use warnings;
  2         13  
  2         67  
3 2     2   10 use strict;
  2         3  
  2         38  
4 2     2   13 use File::Spec;
  2         4  
  2         47  
5 2     2   11 use Carp;
  2         3  
  2         137  
6 2     2   1029 use File::Versions 'make_backup';
  2         2213  
  2         125  
7 2     2   957 use File::Slurper qw/read_text write_text/;
  2         27950  
  2         162  
8 2     2   986 use C::Tokenize '$comment_re';
  2         9064  
  2         329  
9 2     2   1007 use Text::LineNumber;
  2         692  
  2         4571  
10              
11             require Exporter;
12              
13             our @ISA = qw(Exporter);
14              
15             our @EXPORT_OK = qw/
16             add_lines
17             brute_force_line
18             c_string
19             c_to_h_name
20             ch_files
21             convert_to_c_string
22             convert_to_c_string_pc
23             escape_string
24             hash_to_c_file
25             line_directive
26             linein
27             lineout
28             print_bottom_h_wrapper
29             print_top_h_wrapper
30             read_includes
31             remove_quotes
32             stamp_file
33             valid_c_variable
34             /;
35              
36             our %EXPORT_TAGS = (
37             'all' => \@EXPORT_OK,
38             );
39              
40             our $VERSION = '0.010';
41              
42             sub convert_to_c_string
43             {
44 2     2 1 87 my ($text) = @_;
45 2 50       9 if (length ($text) == 0) {
46 0         0 return "\"\"";
47             }
48             # Convert backslashes to double backslashes.
49 2         6 $text =~ s/\\/\\\\/g;
50             # Escape double quotes
51 2         6 $text = escape_string ($text);
52             # If there was a backslash before a quote, as in \", the first
53             # regex above converted it to \\", and then escape_string
54             # converted that to \\\".
55 2         6 $text =~ s/\\\\"/\\"/g;
56             # Remove backslashes from before the @ symbol.
57 2         5 $text =~ s/\\\@/@/g;
58             # Turn each line into a string
59 2         10 $text =~ s/(.*)\n/"$1\\n"\n/gm;
60             # Catch a final line without any \n at its end.
61 2 100       10 if ($text !~ /\\n\"$/) {
62 1         9 $text =~ s/(.+)$/"$1"/g;
63             }
64 2         8 return $text;
65             }
66              
67             sub c_string
68             {
69 0     0 1 0 goto & convert_to_c_string;
70             }
71              
72             sub ch_files
73             {
74 0     0 1 0 my ($c_file_name) = @_;
75 0 0       0 if ($c_file_name !~ /\.c/) {
76 0         0 die "$c_file_name is not a C file name";
77             }
78 0         0 my $h_file_name = $c_file_name;
79 0         0 $h_file_name =~ s/\.c$/\.h/;
80 0 0       0 if (-f $c_file_name) {
81 0         0 make_backup ($c_file_name);
82             }
83 0 0       0 if (-f $h_file_name) {
84 0         0 make_backup ($h_file_name);
85             }
86 0         0 return $h_file_name;
87             }
88              
89             sub convert_to_c_string_pc
90             {
91 1     1 1 474 my ($text) = @_;
92 1         20 $text =~ s/%/%%/g;
93 1         3 return convert_to_c_string ($text);
94             }
95              
96             sub escape_string
97             {
98 2     2 1 4 my ($text) = @_;
99 2         7 $text =~ s/\"/\\\"/g;
100 2         5 return $text;
101             }
102              
103             sub c_to_h_name
104             {
105 0     0 1 0 my ($c_file_name) = @_;
106 0 0       0 if ($c_file_name !~ /\.c/) {
107 0         0 die "$c_file_name is not a C file name";
108             }
109 0         0 my $h_file_name = $c_file_name;
110 0         0 $h_file_name =~ s/\.c$/\.h/;
111 0         0 return $h_file_name;
112             }
113              
114             # This list of reserved words in C is from
115             # http://crasseux.com/books/ctutorial/Reserved-words-in-C.html
116              
117             my @reserved_words = sort {length $b <=> length $a} qw/auto if break
118             int case long char register continue return default short do sizeof
119             double static else struct entry switch extern typedef float union for
120             unsigned goto while enum void const signed volatile/;
121              
122             # A regular expression to match reserved words in C.
123              
124             my $reserved_words_re = join '|', @reserved_words;
125              
126             sub valid_c_variable
127             {
128 0     0 1 0 my ($variable_name) = @_;
129 0 0 0     0 if ($variable_name !~ /^[A-Za-z_][A-Za-z_0-9]+$/ ||
130             $variable_name =~ /^(?:$reserved_words_re)$/) {
131 0         0 return;
132             }
133 0         0 return 1;
134             }
135              
136             # Wrapper name
137             # BKB 2009-10-05 14:09:41
138              
139             sub wrapper_name
140             {
141 0     0 1 0 my ($string) = @_;
142 0         0 $string =~ s/[.-]/_/g;
143 0 0       0 if (! valid_c_variable ($string)) {
144 0         0 croak "Bad string for wrapper '$string'";
145             }
146 0         0 my $wrapper_name = uc $string;
147 0         0 return $wrapper_name;
148             }
149              
150             sub print_top_h_wrapper
151             {
152 0     0 1 0 my ($fh, $file_name) = @_;
153            
154 0         0 my $wrapper_name = wrapper_name ($file_name);
155 0         0 my $wrapper = <
156             #ifndef $wrapper_name
157             #define $wrapper_name
158             EOF
159 0         0 print_out ($fh, $wrapper);
160             }
161              
162             sub print_out
163             {
164 0     0 0 0 my ($fh, $wrapper) = @_;
165 0 0 0     0 if (ref $fh && ref $fh eq 'SCALAR') {
166 0         0 ${$fh} .= $wrapper;
  0         0  
167             }
168             else {
169 0         0 print $fh $wrapper;
170             }
171             }
172              
173             sub print_bottom_h_wrapper
174             {
175 0     0 1 0 my ($fh, $file_name) = @_;
176 0         0 my $wrapper_name = wrapper_name ($file_name);
177 0         0 my $wrapper = <
178             #endif /* $wrapper_name */
179             EOF
180 0         0 print_out ($fh, $wrapper);
181             }
182              
183             sub print_include
184             {
185 0     0 1 0 my ($fh, $h_file_name) = @_;
186 0         0 print $fh <
187             #include "$h_file_name"
188             EOF
189             }
190              
191             sub hash_to_c_file
192             {
193             # $prefix is an optional prefix applied to all variables.
194 0     0 1 0 my ($c_file_name, $hash_ref, $prefix) = @_;
195 0         0 my $h_file_name = ch_files ($c_file_name);
196 0 0       0 die "Not a hash ref" unless ref $hash_ref eq "HASH";
197 0 0       0 $prefix = "" unless $prefix;
198 0 0       0 open my $c_out, ">:utf8", $c_file_name or die $!;
199 0         0 my (undef, undef, $h_file) = File::Spec->splitpath ($h_file_name);
200 0         0 print_include ($c_out, $h_file);
201 0 0       0 open my $h_out, ">:utf8", $h_file_name or die $!;
202 0         0 print_top_h_wrapper ($h_out, $h_file);
203 0         0 for my $variable (sort keys %$hash_ref) {
204 0 0       0 if (! valid_c_variable ($variable)) {
205 0         0 croak "key '$variable' is not a valid C variable";
206             }
207 0         0 my $value = $hash_ref->{$variable};
208 0         0 $value = convert_to_c_string ($value);
209 0         0 print $c_out "const char * $prefix$variable = $value;\n";
210 0         0 print $h_out "extern const char * $prefix$variable; /* $value */\n";
211             }
212 0 0       0 close $c_out or die $!;
213 0         0 print_bottom_h_wrapper ($h_out, $h_file);
214 0 0       0 close $h_out or die $!;
215 0         0 return $h_file_name;
216             }
217              
218             sub line_directive
219             {
220 0     0 1 0 my ($output, $line_number, $file_name) = @_;
221 0 0 0     0 die "$line_number is not a positive integer number"
222             unless $line_number =~ /^[0-9]+$/ && $line_number > 0;
223 0         0 print_out ($output, "#line $line_number \"$file_name\"\n");
224             }
225              
226             sub brute_force_line
227             {
228 0     0 1 0 my ($input_file, $output_file) = @_;
229 0 0       0 open my $input, "<:encoding(utf8)", $input_file or die $!;
230 0 0       0 open my $output, ">:encoding(utf8)", $output_file or die $!;
231 0         0 while (<$input>) {
232 0         0 print $output "#line $. \"$input_file\"\n";
233 0         0 print $output $_;
234             }
235 0 0       0 close $input or die $!;
236 0 0       0 close $output or die $!;
237             }
238              
239             sub add_lines
240             {
241 0     0 1 0 my ($input_file) = @_;
242 0         0 my $full_name = File::Spec->rel2abs ($input_file);
243 0         0 my $text = '';
244 0 0       0 open my $input, "<:encoding(utf8)", $input_file or die $!;
245 0         0 while (<$input>) {
246 0 0       0 if (/^#line/) {
    0          
247 0         0 my $line_no = $. + 1;
248 0         0 $text .= "#line $line_no \"$full_name\"\n";
249             }
250             elsif ($. == 1) {
251 0         0 $text .= "#line 1 \"$full_name\"\n";
252 0         0 $text .= $_;
253             }
254             else {
255 0         0 $text .= $_;
256             }
257             }
258 0         0 return $text;
259             }
260              
261             sub remove_quotes
262             {
263 3     3 1 90 my ($string) = @_;
264 3         27 $string =~ s/^"|"$|"\s*"//g;
265 3         15 return $string;
266             }
267             #use Data::Dumper;
268             sub linedirective
269             {
270 0     0 0   my ($intext, $file, $directive) = @_;
271 0 0 0       die unless $intext && $file && $directive;
      0        
272             # This module is pretty reliable for line numbering.
273 0           my $tln = Text::LineNumber->new ($intext);
274 0           my %renumbered;
275             # Uniquifier for the lines.
276 0           my $count = 0;
277             # This is unlikely to occur.
278 0           my $tag = 'ABRACADABRA';
279             # Watch for blue-moon occurences
280 0 0         die if $intext =~ /$tag\d+/;
281 0           while ($intext =~ s/^\Q$directive/$tag$count$tag/sm) {
282 0           $count++;
283             }
284             # "pos" doesn't work well with s///g, so now we need to match the tags
285             # one by one.
286 0           while ($intext =~ /($tag\d+$tag)/g) {
287 0           my $key = $1;
288 0           my $pos = pos ($intext);
289 0           my $line = $tln->off2lnr ($pos);
290             # print "Position $pos in $file = line $line.\n";
291 0           $renumbered{$key} = $line;
292             }
293             #print Dumper (\%renumbered);
294 0           $intext =~ s/($tag\d+$tag)/#line $renumbered{$1} "$file"/g;
295             # Check for failures. We already checked this doesn't occur
296             # naturally in the file above.
297 0 0         die if $intext =~ /$tag\d+$tag/;
298 0           return $intext;
299             }
300              
301             sub linein
302             {
303 0     0 1   my ($infile) = @_;
304 0           my $intext = read_text ($infile);
305 0           $intext = linedirective ($intext, $infile, '#linein');
306 0           return $intext;
307             }
308              
309             sub lineout
310             {
311 0     0 1   my ($outtext, $outfile) = @_;
312              
313 0           $outtext = linedirective ($outtext, $outfile, "#lineout");
314 0           write_text ($outfile, $outtext);
315             }
316              
317             sub stamp_file
318             {
319 0     0 1   my ($fh, $name) = @_;
320 0 0         if (! defined $name) {
321 0           $name = "This C file";
322             }
323 0           my $now = scalar localtime ();
324 0           my $stamp =<
325             /*
326             $name was generated by $0 at $now.
327             */
328             EOF
329 0           print_out ($fh, $stamp);
330             }
331              
332             sub read_includes
333             {
334 0     0 1   my ($file) = @_;
335 0           my $text = read_text ($file);
336 0           $text =~ s/$comment_re//g;
337 0           my @hfiles;
338 0           while ($text =~ /#include\s*"(.*?)"/g) {
339 0           push @hfiles, $1;
340             }
341 0           return \@hfiles;
342             }
343              
344             1;