File Coverage

blib/lib/FFI/TinyCC/Inline.pm
Criterion Covered Total %
statement 129 140 92.1
branch 23 40 57.5
condition 11 18 61.1
subroutine 19 19 100.0
pod 2 2 100.0
total 184 219 84.0


line stmt bran cond sub pod time code
1             package FFI::TinyCC::Inline;
2              
3 5     5   600794 use strict;
  5         23  
  5         100  
4 5     5   16 use warnings;
  5         8  
  5         76  
5 5     5   94 use 5.010;
  5         12  
6 5     5   2451 use FFI::Platypus;
  5         20217  
  5         149  
7 5     5   1717 use FFI::TinyCC;
  5         239419  
  5         149  
8 5     5   33 use Carp qw( croak );
  5         9  
  5         210  
9 5     5   23 use base qw( Exporter );
  5         20  
  5         2936  
10              
11             our @EXPORT_OK = qw( tcc_inline tcc_eval );
12             our @EXPORT = @EXPORT_OK;
13              
14             # ABSTRACT: Embed Tiny C code in your Perl program
15             our $VERSION = '0.28'; # VERSION
16              
17              
18             my $ffi = FFI::Platypus->new;
19             $ffi->load_custom_type( 'StringArray' => 'string_array' );
20              
21             # TODO: support platypus types like pointers and arrays
22             my %typemap = (
23             'int' => 'int',
24             'signed int' => 'signed int',
25             'unsigned int' => 'unsigned int',
26             'void' => 'void',
27             'short' => 'short',
28             'signed short' => 'signed short',
29             'unsigned short' => 'unsigned short',
30             'long' => 'long',
31             'signed long' => 'signed long',
32             'unsigned long' => 'unsigned long',
33             'char' => 'char',
34             'signed char' => 'signed char',
35             'unsigned char' => 'unsigned char',
36             'float' => 'float',
37             'double' => 'double',
38             'char *' => 'string',
39             );
40              
41             sub _typemap ($)
42             {
43 31     31   46 my($type) = @_;
44 31         42 $type =~ s{^const }{};
45             return $typemap{$type}
46 31 100       132 if defined $typemap{$type};
47 2 50       11 return 'opaque' if $type =~ /\*$/;
48 0         0 croak "unknown type: $type";
49             }
50              
51             sub _generate_sub ($$$)
52             {
53 27     27   43 my($func_name, $func, $tcc) = @_;
54 27         25 my $sub;
55            
56 27         57 my $address = $tcc->get_symbol($func_name);
57            
58 27 100 66     1373 if(@{ $func->{arg_types} } == 2
  27   66     90  
59             && $func->{arg_types}->[0] eq 'int'
60             && $func->{arg_types}->[1] =~ /^(const |)char \*\*$/)
61             {
62 1         3 my $f = $ffi->function($address => ['int','string_array'] => _typemap $func->{return_type});
63             $sub = sub {
64 1     1   7 $f->call(scalar @_, \@_);
65 1         26 };
66             }
67             else
68             {
69 26         35 my $f = $ffi->function($address => [map { _typemap $_ } @{ $func->{arg_types} }] => _typemap $func->{return_type});
  4         8  
  26         58  
70 26     29   1242 $sub = sub { $f->call(@_) };
  29         1189  
71             }
72            
73 27         44 $sub;
74             }
75              
76              
77             sub import
78             {
79 10     10   73 my($class, @rest) = @_;
80            
81 10 100 66     59 if(defined $rest[0] && defined $rest[1]
      66        
82             && $rest[0] eq 'options')
83             {
84 6 50       17 if($] >= 5.010)
85             {
86 6         7 shift @rest;
87 6         18 $^H{"FFI::TinyCC::Inline/options"} = shift @rest;
88             }
89             else
90             {
91 0         0 croak "options not supported on Perl 5.8";
92             }
93             }
94            
95 10 100       3902 return unless @rest > 0;
96              
97 4         10 @_ = ($class, @rest);
98 4         3604 goto &Exporter::import;
99             }
100              
101              
102             sub tcc_inline ($)
103             {
104 7     7 1 9022 my($code) = @_;
105 7         15 my $caller = caller;
106            
107 7         26 my $tcc = FFI::TinyCC->new(_no_free_store => 1);
108            
109 7         2409 my $h = (caller(0))[10];
110 7 100       23 if($h->{"FFI::TinyCC::Inline/options"})
111 5         13 { $tcc->set_options($h->{"FFI::TinyCC::Inline/options"}) }
112              
113 7         90 $tcc->compile_string($code);
114 6         293 my $meta = FFI::TinyCC::Parser->extract_function_metadata($code);
115 6         9 foreach my $func_name (keys %{ $meta->{functions} })
  6         15  
116             {
117 20         35 my $sub = _generate_sub($func_name, $meta->{functions}->{$func_name}, $tcc);
118 5     5   30 no strict 'refs';
  5         9  
  5         934  
119 20         32 *{join '::', $caller, $func_name} = $sub;
  20         55  
120             }
121 6         32 ();
122             }
123              
124              
125             sub tcc_eval ($;@)
126             {
127 8     8 1 6092 my($code, @args) = @_;
128 8         32 my $tcc = FFI::TinyCC->new;
129            
130 8         2487 my $h = (caller(0))[10];
131 8 100       28 if($h->{"FFI::TinyCC::Inline/options"})
132 5         14 { $tcc->set_options($h->{"FFI::TinyCC::Inline/options"}) }
133              
134 8         76 $tcc->compile_string($code);
135 7         249 my $meta = FFI::TinyCC::Parser->extract_function_metadata($code);
136 7         10 my $func = $meta->{functions}->{main};
137 7 50       13 croak "no main function" unless defined $func;
138 7         16 my $sub = _generate_sub('main', $meta->{functions}->{main}, $tcc);
139 7         14 $sub->(@args);
140             }
141              
142             package
143             FFI::TinyCC::Parser;
144              
145             # this parser code stolen shamelessly
146             # from XS::TCC, which I strongly suspect
147             # was itself shamelessly "borrowed"
148             # from Inline::C::Parser::RegExp
149              
150             # Copyright 2002 Brian Ingerson
151             # Copyright 2008, 2010-2012 Sisyphus
152             # Copyright 2013 Steffen Muellero
153              
154             # This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
155              
156 5     5   38 use strict;
  5         6  
  5         99  
157 5     5   28 use warnings;
  5         13  
  5         3584  
158              
159             # These regular expressions were derived from Regexp::Common v0.01.
160             my $RE_comment_C = q{(?:(?:\/\*)(?:(?:(?!\*\/)[\s\S])*)(?:\*\/))};
161             my $RE_comment_Cpp = q{(?:\/\*(?:(?!\*\/)[\s\S])*\*\/|\/\/[^\n]*\n)};
162             my $RE_quoted = (q{(?:(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\")}
163             .q{|(?:\')(?:[^\\\']*(?:\\.[^\\\']*)*)(?:\'))});
164             my $RE_balanced_brackets;
165             $RE_balanced_brackets =
166             qr'(?:[{]((?:(?>[^{}]+)|(??{$RE_balanced_brackets}))*)[}])';
167             my $RE_balanced_parens;
168             $RE_balanced_parens =
169             qr'(?:[(]((?:(?>[^()]+)|(??{$RE_balanced_parens}))*)[)])';
170              
171              
172             sub _normalize_type {
173             # Normalize a type for lookup in a typemap.
174 33     33   38 my($type) = @_;
175              
176             # Remove "extern".
177             # But keep "static", "inline", "typedef", etc,
178             # to cause desirable typemap misses.
179 33         38 $type =~ s/\bextern\b//g;
180              
181             # Whitespace: only single spaces, none leading or trailing.
182 33         72 $type =~ s/\s+/ /g;
183 33         53 $type =~ s/^\s//; $type =~ s/\s$//;
  33         51  
184              
185             # Adjacent "derivative characters" are not separated by whitespace,
186             # but _are_ separated from the adjoining text.
187             # [ Is really only * (and not ()[]) needed??? ]
188 33         41 $type =~ s/\*\s\*/\*\*/g;
189 33         34 $type =~ s/(?<=[^ \*])\*/ \*/g;
190              
191 33         43 return $type;
192             }
193              
194             sub extract_function_metadata {
195 13     13   28 my (undef, $code) = @_;
196              
197 13         38 my $results = {
198             function_names => [],
199             functions => {},
200             };
201              
202             # First, we crush out anything potentially confusing.
203             # The order of these _does_ matter.
204 13         93 $code =~ s/$RE_comment_C/ /go;
205 13         111 $code =~ s/$RE_comment_Cpp/ /go;
206 13         21 $code =~ s/^\#.*(\\\n.*)*//mgo;
207             #$code =~ s/$RE_quoted/\"\"/go; # Buggy, if included.
208 13         90 $code =~ s/$RE_balanced_brackets/{ }/go;
209              
210             # The decision of what is an acceptable declaration was originally
211             # derived from Inline::C::grammar.pm version 0.30 (Inline 0.43).
212              
213 13         37 my $re_plausible_place_to_begin_a_declaration = qr {
214             # The beginning of a line, possibly indented.
215             # (Accepting indentation allows for C code to be aligned with
216             # its surrounding perl, and for backwards compatibility with
217             # Inline 0.43).
218             (?m: ^ ) \s*
219             }xo;
220              
221             # Instead of using \s , we dont tolerate blank lines.
222             # This matches user expectation better than allowing arbitrary
223             # vertical whitespace.
224 13         36 my $sp = qr{[ \t]|\n(?![ \t]*\n)};
225              
226 13         114 my $re_type = qr {(
227             (?: \w+ $sp* )+? # words
228             (?: \* $sp* )* # stars
229             )}xo;
230              
231 13         62 my $re_identifier = qr{ (\w+) $sp* }xo;
232 13         423 while( $code =~ m{
233             $re_plausible_place_to_begin_a_declaration
234             ( $re_type $re_identifier $RE_balanced_parens $sp* (\;|\{) )
235             }xgo)
236             {
237 27         86 my($type, $identifier, $args, $what) = ($2,$3,$4,$5);
238 27 50       53 $args = "" if $args =~ /^\s+$/;
239              
240 27         30 my $need_threading_context = 0;
241 27         34 my $is_decl = $what eq ';';
242 27         23 my $function = $identifier;
243 27         44 my $return_type = _normalize_type($type);
244 27         49 my @arguments = split ',', $args;
245              
246             #goto RESYNC if $is_decl && !$self->{data}{AUTOWRAP};
247 27 50       48 goto RESYNC if exists $results->{functions}{$function};
248             #goto RESYNC if !defined $self->{data}{typeconv}{valid_rtypes}{$return_type};
249              
250 27         27 my(@arg_names,@arg_types);
251 27         31 my $dummy_name = 'arg1';
252              
253 27         27 my $argno = 0;
254 27         37 foreach my $arg (@arguments) {
255             # recognize threading context passing as part of first arg
256 6 50 66     17 if ($argno++ == 0 and $arg =~ s/^\s*pTHX_?\s*//) {
257 0         0 $need_threading_context = 1;
258 0 0       0 next if $arg !~ /\S/;
259             }
260              
261 6         7 my $arg_no_space = $arg;
262 6         15 $arg_no_space =~ s/\s+//g;
263              
264             # If $arg_no_space is 'void', there will be no identifier.
265 6 50       72 if( my($type, $identifier) =
    0          
266             $arg =~ /^\s*$re_type(?:$re_identifier)?\s*$/o )
267             {
268 6         8 my $arg_name = $identifier;
269 6         7 my $arg_type = _normalize_type($type);
270              
271 6 50 33     12 if((!defined $arg_name) && ($arg_no_space ne 'void')) {
272 0 0       0 goto RESYNC if !$is_decl;
273 0         0 $arg_name = $dummy_name++;
274             }
275             #goto RESYNC if ((!defined
276             # $self->{data}{typeconv}{valid_types}{$arg_type}) && ($arg_no_space ne 'void'));
277              
278             # Push $arg_name onto @arg_names iff it's defined. Otherwise ($arg_no_space
279             # was 'void'), push the empty string onto @arg_names (to avoid uninitialized
280             # warnings emanating from C.pm).
281 6 50       10 defined($arg_name) ? push(@arg_names,$arg_name)
282             : push(@arg_names, '');
283 6 50       7 if($arg_name) {push(@arg_types,$arg_type)}
  6         11  
284 0         0 else {push(@arg_types,'')} # $arg_no_space was 'void' - this push() avoids 'uninitialized' warnings from C.pm
285             }
286             elsif($arg =~ /^\s*\.\.\.\s*$/) {
287 0         0 push(@arg_names,'...');
288 0         0 push(@arg_types,'...');
289             }
290             else {
291 0         0 goto RESYNC;
292             }
293             }
294              
295             # Commit.
296 27         28 push @{$results->{function_names}}, $function;
  27         47  
297 27         58 $results->{functions}{$function}{return_type}= $return_type;
298 27         43 $results->{functions}{$function}{arg_names} = [@arg_names];
299 27         38 $results->{functions}{$function}{arg_types} = [@arg_types];
300 27 50       36 $results->{functions}{$function}{need_threading_context} = $need_threading_context if $need_threading_context;
301              
302 27         975 next;
303              
304 0         0 RESYNC: # Skip the rest of the current line, and continue.
305             $code =~ /\G[^\n]*\n/gc;
306             }
307              
308 13         39 return $results;
309             }
310              
311             1;
312              
313             __END__