File Coverage

blib/lib/C/Tokenize.pm
Criterion Covered Total %
statement 90 93 96.7
branch 19 22 86.3
condition 3 3 100.0
subroutine 9 9 100.0
pod 3 5 60.0
total 124 132 93.9


line stmt bran cond sub pod time code
1             package C::Tokenize;
2 4     4   260578 use warnings;
  4         15  
  4         153  
3 4     4   26 use strict;
  4         9  
  4         88  
4 4     4   22 use Carp;
  4         12  
  4         4423  
5             require Exporter;
6             our @ISA = qw(Exporter);
7             our @EXPORT_OK = qw/
8             $char_const_re
9             $comment_re
10             $cpp_re
11             $cvar_re
12             $cxx_comment_re
13             $decimal_re
14             $fargs_re
15             $grammar_re
16             $hex_re
17             $include
18             $include_local
19             $number_re
20             $octal_re
21             $operator_re
22             $reserved_re
23             $single_string_re
24             $string_re
25             $trad_comment_re
26             $word_re
27             @fields
28             decomment
29             function_arg
30             remove_quotes
31             strip_comments
32             tokenize
33             /;
34              
35             our %EXPORT_TAGS = (
36             all => \@EXPORT_OK,
37             );
38              
39             our $VERSION = '0.17';
40              
41             # http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
42             # 6.4.1
43              
44             my @reserved_words = sort {length $b <=> length $a}
45             qw/
46             auto
47             break
48             case
49             char
50             const
51             continue
52             default
53             do
54             double
55             else
56             enum
57             extern
58             float
59             for
60             goto
61             if
62             inline
63             int
64             long
65             register
66             restrict
67             return
68             short
69             signed
70             sizeof
71             static
72             struct
73             switch
74             typedef
75             union
76             unsigned
77             void
78             volatile
79             while
80             _Bool
81             _Complex
82             _Imaginary
83             /;
84              
85             my $reserved_words = join '|', @reserved_words;
86             our $reserved_re = qr/\b(?:$reserved_words)\b/;
87              
88             our @fields = qw/comment cpp char_const operator grammar
89             number word string reserved/;
90              
91             # Regular expression to match a /* */ C comment.
92              
93             our $trad_comment_re = qr!
94             /\*
95             (?:
96             # Match "not an asterisk"
97             [^*]
98             |
99             # Match multiple asterisks followed
100             # by anything except an asterisk or a
101             # slash.
102             \*+[^*/]
103             )*
104             # Match multiple asterisks followed by a
105             # slash.
106             \*+/
107             !x;
108              
109             # Regular expression to match a // C comment (C++-style comment).
110              
111             our $cxx_comment_re = qr!//.*\n!;
112              
113             # Master comment regex
114              
115             our $comment_re = qr/
116             (?:
117             $trad_comment_re
118             |
119             $cxx_comment_re
120             )
121             /x;
122              
123             # Regular expression to match a C preprocessor instruction.
124              
125             our $cpp_re = qr/^\h*
126             \#
127             (?:
128             $trad_comment_re
129             |
130             [^\\\n]
131             |
132             \\[^\n]
133             |
134             \\\n
135             )+\n
136             /mx;
137              
138             # Regular expression to match a C character constant like 'a' or '\0'.
139             # This allows any \. expression at all.
140              
141             our $char_const_re = qr/
142             '
143             (?:
144             .
145             |
146             \\.
147             )
148             '
149             /x;
150              
151             # Regular expression to match one character operators
152              
153             our $one_char_op_re = qr/(?:\%|\&|\+|\-|\=|\/|\||\.|\*|\:|>|<|\!|\?|~|\^)/;
154              
155             # Regular expression to match all operators
156              
157             our $operator_re = qr/
158             (?:
159             # Operators with two characters
160             \|\||&&|<<|>>|--|\+\+|->|==
161             |
162             # Operators with one or two characters
163             # followed by an equals sign.
164             (?:<<|>>|\+|-|\*|\/|%|&|\||\^)
165             =
166             |
167             $one_char_op_re
168             )
169             /x;
170              
171             # Re to match a C number
172              
173             our $octal_re = qr/0[0-7]+/;
174              
175             our $decimal_re = qr/[-+]?([0-9]*\.)?[0-9]+([eE][-+]?[0-9]+)?l?/i;
176              
177             our $hex_re = qr/0x[0-9a-f]+l?/i;
178              
179             our $number_re = qr/
180             (?:
181             $hex_re
182             |
183             $decimal_re
184             |
185             $octal_re
186             )
187             /x;
188              
189             # Re to match a C word
190              
191             our $word_re = qr/[a-z_](?:[a-z_0-9]*)/i;
192              
193             # Re to match C grammar
194              
195             our $grammar_re = qr/[(){};,\[\]]/;
196              
197             # Regular expression to match a C string.
198              
199             our $single_string_re = qr/
200             (?:
201             "
202             (?:[^\\"]+|\\[^"]|\\")*
203             "
204             )
205             /x;
206              
207              
208             # Compound string regular expression.
209              
210             our $string_re = qr/$single_string_re(?:\s*$single_string_re)*/;
211              
212             # Master regular expression for tokenizing C text. This uses named
213             # captures.
214            
215             our $c_re = qr/
216             (?\s+)?
217             (?:
218             (?$comment_re)
219             |
220             (?$cpp_re)
221             |
222             (?$char_const_re)
223             |
224             (?$operator_re)
225             |
226             (?$grammar_re)
227             |
228             (?$number_re)
229             |
230             (?$reserved_re)
231             |
232             (?$word_re)
233             |
234             (?$string_re)
235             )
236             /x;
237              
238              
239             # Match for '#include "file.h"'. This captures the entire #include
240             # statement in $1 and the file name in $2.
241              
242             our $include_local = qr/
243             ^
244             (\#
245             \s*
246             include
247             \s*
248             "((?:[^"]|\\"))"
249             )
250             (\s|$comment_re)*
251             $
252             /smx;
253              
254             our $include = qr/
255             ^
256             (\#
257             \s*
258             include
259             \s*
260             ["<]
261             ([a-zA-Z0-9\-]+\.h)
262             [">]
263             )
264             (\s|$comment_re)*
265             $
266             /smx;
267              
268             my $deref = qr!
269             [\*&]+\s*$word_re
270             !x;
271              
272             my $array_re = qr!
273             $word_re
274             \s*
275             \[
276             \s*
277             $word_re
278             \s*
279             \]
280             !x;
281              
282             my $member = qr!
283             (?:
284             (?:
285             ->
286             |
287             \.
288             )
289             $word_re
290             |
291             $array_re
292             )
293             !x;
294              
295             # Any C variable which can be used as an lvalue or a function argument.
296              
297             our $cvar_re = qr!
298             (?:
299             # Any deferenced value
300             $deref
301             |
302             # A word or a dereferenced value in brackets
303             (?:
304             $word_re
305             |
306             $array_re
307             |
308             \(\s*$deref\)
309             )
310             # Followed by zero or more struct member
311             $member*
312             )
313             !x;
314              
315             # Function arguments
316              
317             our $fargs_re = qr!
318             \(
319             (?:
320             \s*$cvar_re\s*,
321             )*
322             (?:\s*$cvar_re\s*)?
323             \)
324             !x;
325              
326             sub decomment
327             {
328 2     2 1 14554 my ($comment) = @_;
329 2         25 $comment =~ s/^\/\*(.*)\*\/$/$1/sm;
330 2         8 return $comment;
331             }
332              
333             sub tokenize
334             {
335 3     3 1 13466 my ($text) = @_;
336              
337             # This array contains array references, each of which is a pair of
338             # start and end points of a line in $text.
339              
340 3         25 my @lines = get_lines ($text);
341              
342             # The tokens the input is broken into.
343              
344 3         7 my @tokens;
345              
346 3         7 my $line = 1;
347 3         787 while ($text =~ /\G($c_re)/g) {
348 32         80 my $match = $1;
349 32 50       86 if ($match =~ /^\s+$/s) {
350 0         0 die "Bad match.\n";
351             }
352             # Add one to the line number for each newline.
353 32         155 while ($match =~ /\n/g) {
354 11         23 $line++;
355             }
356 32         36 my %element;
357             # Store the whitespace in front of the element.
358 4 100   4   1281 if ($+{leading}) {
  4         1434  
  4         2655  
  32         144  
359 15         57 $element{leading} = $+{leading};
360             }
361             else {
362 17         30 $element{leading} = '';
363             }
364 32         64 $element{line} = $line;
365 32         40 my $matched;
366 32         50 for my $field (@fields) {
367 186 100       591 if (defined $+{$field}) {
368 32         52 $element{type} = $field;
369 32         103 $element{$field} = $+{$field};
370 32         53 $matched = 1;
371 32         43 last;
372             }
373             }
374 32 50       55 if (! $matched) {
375 0         0 die "Bad regex $line: '$match'\n";
376             }
377              
378 32         266 push @tokens, \%element;
379             }
380              
381 3         24 return \@tokens;
382             }
383              
384             # The return value is an array containing start and end points of the
385             # lines in $text.
386              
387             sub get_lines
388             {
389 3     3 0 10 my ($text) = @_;
390 3         7 my @lines;
391 3         6 my $start = 0;
392 3         6 my $end;
393 3         6 my $line = 1;
394 3         39 while ($text =~ /\n/g) {
395 12         25 $end = pos $text;
396 12         42 $lines[$line] = {start => $start, end => $end};
397 12         24 $line++;
398 12         34 $start = $end + 1;
399             }
400 3         12 return @lines;
401             }
402              
403             sub function_arg
404             {
405 1     1 0 8381 my ($c) = @_;
406 1         3 my $tokens = tokenize ($c);
407 1         2 my @args;
408             # Number of ('s minus number of )'s.
409 1         2 my $depth = 0;
410 1         2 my $arg = '';
411 1         2 for (@$tokens) {
412 20         35 my $type = $_->{type};
413 20         30 my $value = $_->{$type};
414 20 100 100     59 if ($depth == 1 && $value eq ',') {
415 4         13 $arg =~ s/^\s+//;
416 4         8 push @args, $arg;
417 4         6 $arg = '';
418 4         8 next;
419             }
420 16 100       29 if ($value eq '(') {
421 3         4 $depth++;
422 3 100       7 if ($depth == 1) {
423 1         4 $arg =~ s/^\s+//;
424 1         3 push @args, $arg;
425 1         3 $arg = '';
426 1         2 next;
427             }
428             }
429 15 100       29 if ($value eq ')') {
430 3         5 $depth--;
431             # Push final argument before the last ) of the function's
432             # arguments.
433 3 100       7 if ($depth == 0) {
434 1         7 $arg =~ s/^\s+//;
435 1         4 push @args, $arg;
436 1         4 $arg = '';
437 1         3 next;
438             }
439             }
440 14         31 $arg .= $_->{leading} . $value;
441             }
442 1 50       5 if (! wantarray ()) {
443 0         0 carp "Return value of function_arg is array";
444             }
445 1         20 return @args;
446             }
447              
448             # This comes from XS::Check, moved here because it might be useful for
449             # other C projects.
450              
451             sub strip_comments
452             {
453 4     4 1 11537 my ($xs) = @_;
454             # Remove trad comments but keep the line numbering. Trad comments
455             # are deleted before C++ comments, see below for explanation.
456 4         142 while ($xs =~ /($single_string_re|$trad_comment_re|$cxx_comment_re)/g) {
457 10         22 my $comment = $1;
458 10 100       33 if ($comment =~ /^".*"$/) {
459 5         28 next;
460             }
461             # If the C comment consists of int/* comment */x;, it compiles
462             # OK, but if /* comment */ is completely removed then intx;
463             # doesn't compile, so at minimum substitute one space
464             # character for each comment.
465 5         7 my $subs = ' ';
466 5         17 while ($comment =~ /([\n\r])/g) {
467 4         15 $subs .= $1;
468             }
469 5         99 $xs =~ s/\Q$comment\E/$subs/;
470             }
471 4         11 return $xs;
472             }
473              
474             1;