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