File Coverage

blib/lib/Devel/Tokenizer/C.pm
Criterion Covered Total %
statement 156 159 98.1
branch 77 86 89.5
condition 32 37 86.4
subroutine 14 14 100.0
pod 3 3 100.0
total 282 299 94.3


line stmt bran cond sub pod time code
1             ################################################################################
2             #
3             # MODULE: Devel::Tokenizer::C
4             #
5             ################################################################################
6             #
7             # DESCRIPTION: Generate C source for fast keyword tokenizer
8             #
9             ################################################################################
10             #
11             # $Project: /Devel-Tokenizer-C $
12             # $Author: mhx $
13             # $Date: 2008/12/13 16:03:38 +0100 $
14             # $Revision: 16 $
15             # $Source: /lib/Devel/Tokenizer/C.pm $
16             #
17             ################################################################################
18             #
19             # Copyright (c) 2002-2008 Marcus Holland-Moritz. All rights reserved.
20             # This program is free software; you can redistribute it and/or modify
21             # it under the same terms as Perl itself.
22             #
23             ################################################################################
24              
25             package Devel::Tokenizer::C;
26              
27 4     4   18431 use 5.005_03;
  4         59  
  4         171  
28 4     4   23 use strict;
  4         9  
  4         126  
29 4     4   29 use Carp;
  4         8  
  4         315  
30 4     4   23 use vars '$VERSION';
  4         8  
  4         11131  
31              
32             $VERSION = do { my @r = '$Snapshot: /Devel-Tokenizer-C/0.08 $' =~ /(\d+\.\d+(?:_\d+)?)/; @r ? $r[0] : '9.99' };
33              
34             my %DEF = (
35             CaseSensitive => 1,
36             Comments => 1,
37             Indent => ' ',
38             MergeSwitches => 0,
39             Strategy => 'ordered', # wide, narrow, ordered
40             StringLength => '',
41             TokenEnd => "'\\0'",
42             TokenFunc => sub { "return $_[0];\n" },
43             # TokenSort => sub { $_[0] cmp $_[1] }, # TODO?
44             TokenString => 'tokstr',
45             UnknownLabel => 'unknown',
46             UnknownCode => undef,
47             );
48              
49             sub new
50             {
51 133     133 1 291457316 my $class = shift;
52 133         1826 my %opt = @_;
53 133 100       675 for (keys %opt) { exists $DEF{$_} or croak "Invalid option '$_'" }
  744         3104  
54 132 100       903 if (exists $opt{TokenFunc}) {
55 130 100       967 ref $opt{TokenFunc} eq 'CODE'
56             or croak "Option TokenFunc needs a code reference";
57             }
58 131         5416 my %self = (
59             %DEF, @_,
60             __tcheck__ => {},
61             __tokens__ => {},
62             __backup__ => [],
63             __maxlen__ => 0,
64             );
65 131 50 66     1082 if ($self{StringLength} eq '' and $self{Strategy} ne 'ordered') {
66 0         0 croak "Cannot use Strategy '$self{Strategy}' without StringLength";
67             }
68 131         1137 bless \%self, $class;
69             }
70              
71             sub add_tokens
72             {
73 64141     64141 1 948289 my $self = shift;
74 64141 100       221800 my($tokens, $pre) = ref $_[0] eq 'ARRAY' ? @_ : \@_;
75 64141         133799 for (@$tokens) {
76 64143 100       184393 my $tok = $self->{CaseSensitive} ? $_ : lc;
77 64143 100 100     213400 exists $self->{__tcheck__}{$tok}
    100          
78             and carp $self->{__tcheck__}{$tok} eq ($pre || '')
79             ? "Multiple definition of token '$_'"
80             : "Redefinition of token '$_'";
81 64143   100     457271 $self->{__tcheck__}{$tok} = $self->{__tokens__}{$_} = $pre || '';
82 64143         136911 my $len = length __quotecomment__($_);
83 64143 100       316309 $self->{__maxlen__} = $len if $len > $self->{__maxlen__};
84             }
85 64141         187499 $self;
86             }
87              
88             sub generate
89             {
90 130     130 1 4475 my $self = shift;
91 130         659 my %options = (Indent => '', @_);
92 130         336 my $IND = $options{Indent};
93 130         411 my $I = $self->{Indent};
94              
95 130 100       515 if ($self->{StringLength}) {
96 96         235 my @tokens;
97 96         276 for my $t (keys %{$self->{__tokens__}}) {
  96         18826  
98 48096         146167 $tokens[length $t]{$t} = $self->{__tokens__}{$t};
99             }
100              
101 96         5392 my $rv = <
102             ${IND}switch ($self->{StringLength})
103             $IND\{
104             EOS
105              
106 96         459 for my $len (1 .. $#tokens) {
107 1728 100       8912 $tokens[$len] or next;
108 1536         7837 my $count = keys %{$tokens[$len]};
  1536         9778  
109 1536         27652 my $switch = $self->__makeit__($IND.$I.$I, $self->__order__($tokens[$len]), 0, 0, $tokens[$len]);
110 1536         94027 $rv .= <
111             $IND${I}case $len: /* $count tokens of length $len */
112             $switch
113             EOS
114             }
115              
116 96         712 my $unk = $self->__unknown__("$IND$I$I");
117              
118 96 50       662 $rv .= <
119             $IND${I}default:
120             $unk
121             EOS
122              
123 96         67085 $rv .= "$IND}\n"
124             }
125             else {
126 34         269 return $self->__makeit__($IND, undef, 0, 0, $self->{__tokens__});
127             }
128             }
129              
130             sub __order__
131             {
132 1536     1536   3635 my($self, $tok) = @_;
133 1536         3820 my @hist;
134              
135 1536 100       11439 return undef if $self->{Strategy} eq 'ordered';
136              
137 1024         16472 for my $k (keys %$tok) {
138 32064 100       305595 my @key = ($self->{CaseSensitive} ? $k : uc $k) =~ /(.)/g;
139 32064         75635 for my $i (0 .. $#key) {
140 300544         598279 $hist[$i]{$key[$i]}++;
141             }
142             }
143 1024         6435 for my $i (0 .. $#hist) {
144 10752         26399 $hist[$i]{ix} = $i;
145             }
146              
147 1024 100       7234 if ($self->{Strategy} eq 'wide') {
    50          
148 512         2934 @hist = sort { keys %$b <=> keys %$a } @hist;
  13360         26001  
149             }
150             elsif ($self->{Strategy} eq 'narrow') {
151 512         2763 @hist = sort { keys %$a <=> keys %$b } @hist;
  11600         16983  
152             }
153             else {
154 0         0 croak "Invalid Strategy '$self->{Strategy}'";
155             }
156              
157 1024         28313 return [map $_->{ix}, @hist];
158             }
159              
160             sub __commented__
161             {
162 64129     64129   128985 my($self, $code, $comment) = @_;
163 64129 50       231841 return "$code\n" unless $self->{Comments};
164 64129         230595 sprintf "%-50s/* %-$self->{__maxlen__}s */\n", $code, __quotecomment__($comment);
165             }
166              
167             sub __unknown__
168             {
169 93537     93537   171325 my($self, $indent) = @_;
170 93537 100       344888 my $code = defined $self->{UnknownCode} ? $self->{UnknownCode}
171             : "goto $self->{UnknownLabel};";
172 93537         282874 $code =~ s/\s+$//;
173 93537         371913 $code =~ s/^/$indent/m;
174 93537         332223 return $code;
175             }
176              
177             sub __makeit__
178             {
179 94370     94370   285430 my($self, $IND, $order, $level, $pre_flag, $t, %tok) = @_;
180 94370         214563 my $I = $self->{Indent};
181              
182 94370 100       245199 %$t or return '';
183              
184 94369 100       287449 if (keys(%$t) == 1) {
185 64129         164805 my($token) = keys %$t;
186 64129         103338 my($rvs,$code);
187 64129         101367 my $unknown = '';
188              
189 64129 100       158015 if ($level > length $token) {
190 144         636 $rvs = $self->__commented__($IND.'{', $token);
191 144         668 $code = $self->{TokenFunc}->($token);
192 144         2303 $code =~ s/^/$IND$I/mg;
193             }
194             else {
195 63985         758923 my @chars = $token =~ /(.)/g;
196 435523         1101110 my $cmp = join " &&\n$IND$I$I",
197 780371 100       1204402 map { $_->[1] } sort { $a->[0] <=> $b->[0] } map {
  435523         1072705  
198 63985         284623 my $p = defined $order ? $order->[$_] : $_;
199 435523         1067631 [$p, $self->__chr2cmp__($p, "'".__quotechar__($chars[$p])."'")];
200             } $level .. $#chars;
201              
202 63985 100 66     693535 if (defined $self->{TokenEnd} and not $self->{StringLength}) {
203 15889         25227 $level = @chars;
204 15889 100       63364 $cmp and $cmp .= " &&\n$IND$I$I";
205 15889         57222 $cmp .= $self->{TokenString} . "[$level] == $self->{TokenEnd}";
206             }
207              
208 63985 50       299762 $unknown = "\n" . $self->__unknown__($IND) . "\n" if $cmp;
209              
210 63985 50       420987 $rvs = ($cmp ? $IND . "if ($cmp)\n" : '') . $self->__commented__($IND.'{', $token);
211              
212 63985         351804 $code = $self->{TokenFunc}->($token);
213 63985         1267238 $code =~ s/^/$IND$I/mg;
214             }
215              
216 64129         499639 return "$rvs$code$IND}\n$unknown";
217             }
218              
219 30240         141027 for my $n (keys %$t) {
220 165712 100 66     682592 my $c = __quotechar__(substr $n, (defined $order ? $order->[$level] : $level), 1)
    50          
221             or defined $self->{TokenEnd} or next;
222 165712 100 100     2064935 $tok{$c ne '' ? ($self->{CaseSensitive} || $c !~ /^[a-zA-Z]$/ ? "'$c'" : "'\U$c\E'")
    100          
223             : $self->{TokenEnd}}{$n} = $t->{$n};
224             }
225              
226 30240 100       146749 my $pos = defined $order ? $order->[$level] : $level;
227 30240         67890 my $bke = '';
228 30240         58050 my $rvs = '';
229 30240         65451 my $nlflag = 0;
230              
231 30240 100 100     155750 if (keys %tok > 1 or !$self->{MergeSwitches}) {
232 27888 100       42710 if (@{$self->{__backup__}}) {
  27888         117419  
233 2352         10879 my $cmp = join " &&\n$IND$I$I",
234 1080         3173 map { $_->[1] } sort { $a->[0] <=> $b->[0] }
  1568         6989  
235 1568         5902 @{$self->{__backup__}};
236            
237 1568         10570 $rvs .= $IND."if ($cmp)\n".$IND."{\n";
238 1568         3723 $bke = "$IND}\n";
239              
240 1568         3785 $IND .= $I;
241              
242 1568         3352 @{$self->{__backup__}} = ();
  1568         6861  
243             }
244              
245 27888         131638 $rvs .= $IND."switch ($self->{TokenString}\[$pos])\n".$IND."{\n";
246             }
247             else {
248 2352 100       5169 $bke = "\n" . $self->__unknown__($IND) . "\n" unless @{$self->{__backup__}};
  2352         15372  
249 2352         5584 push @{$self->{__backup__}}, [$pos, $self->__chr2cmp__($pos, keys %tok)];
  2352         12727  
250             }
251              
252 30240         169236 for my $c (sort keys %tok) {
253 92800         208049 my($clear_pre_flag, %seen) = 0;
254 92800         162561 my @pre = grep !$seen{$_}++, values %{$tok{$c}};
  92800         770030  
255              
256 92800 100       281474 $nlflag and $rvs .= "\n";
257              
258 92800 100 66     678148 if( $pre_flag == 0 && @pre == 1 && $pre[0] ) {
      66        
259 12294         60663 $rvs .= "#if $pre[0]\n";
260 12294         26401 $pre_flag = $clear_pre_flag = 1;
261             }
262              
263 92800 100 100     350737 if (keys %tok > 1 or !$self->{MergeSwitches}) {
264 90448 100 100     725372 $rvs .= $self->{CaseSensitive} || $c !~ /^'[a-zA-Z]'$/
265             ? $IND.$I."case $c:\n"
266             : $IND.$I."case \U$c\E:\n"
267             . $IND.$I."case \L$c\E:\n";
268              
269 90448         509299 $rvs .= $self->__makeit__($IND.$I.$I, $order, $level+1, $pre_flag, $tok{$c});
270             }
271             else {
272 2352         10694 $rvs .= $self->__makeit__($IND, $order, $level+1, $pre_flag, $tok{$c});
273             }
274              
275 92800 100       338289 if ($clear_pre_flag) {
276 12294 50       68159 my $cmt = $self->{Comments} ? " /* $pre[0] */" : '';
277 12294         30065 $rvs .= "#endif$cmt\n";
278 12294         28076 $pre_flag = 0;
279             }
280              
281 92800         380103 $nlflag = 1;
282             }
283              
284 30240 100 100     224492 if (keys %tok > 1 || !$self->{MergeSwitches}) {
285 27888         142640 my $unk = $self->__unknown__("$IND$I$I");
286              
287 27888 50       132036 $unk = "$IND${I}default:\n$unk\n" if $unk;
288              
289 27888         723325 return <
290             $rvs
291             $unk$IND}
292             EOS
293             }
294             else {
295 2352         25768 return $rvs . $bke;
296             }
297             }
298              
299             sub __quotechar__
300             {
301 601235     601235   1130182 my $str = shift;
302 601235         1096388 $str =~ s/(['\\])/\\$1/g;
303 601235         1117453 return __quotecomment__($str);
304             }
305              
306             sub __quotecomment__
307             {
308 729507     729507   1267699 my $str = shift;
309 729507         1316103 for my $c (qw( a b f n r t )) {
310 4377042         228265162 my $e = eval qq("\\$c");
311 4377042         46964845 $str =~ s/$e/\\$c/g;
312             }
313 729507         1603642 $str =~ s/([\x01-\x1F])/sprintf "\\%o", ord($1)/eg;
  0         0  
314 729507         3360038 return $str;
315             }
316              
317             sub __chr2cmp__
318             {
319 437875     437875   889424 my($self, $p, $c) = @_;
320 437875 100 100     4971812 $self->{CaseSensitive} || $c !~ /^'[a-zA-Z]'$/
321             ? $self->{TokenString}."[$p] == $c"
322             : '(' . $self->{TokenString} . "[$p] == \U$c\E || "
323             . $self->{TokenString} . "[$p] == \L$c\E)";
324             }
325              
326             1;
327              
328             __END__