File Coverage

blib/lib/XS/Writer.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package XS::Writer;
2              
3 1     1   1021 use strict;
  1         3  
  1         46  
4 1     1   6 use warnings;
  1         3  
  1         60  
5              
6             our $VERSION = 0.02;
7              
8 1     1   16 use File::Basename;
  1         2  
  1         110  
9 1     1   6 use File::Path;
  1         8  
  1         58  
10 1     1   6 use Carp;
  1         2  
  1         71  
11 1     1   524 use Moose;
  0            
  0            
12             use Moose::Autobox;
13              
14             {
15             package StringWithWhitespace;
16             use Moose::Role;
17              
18             sub strip_ws {
19             $_[0] =~ s/^\s+//;
20             $_[0] =~ s/\s+$//;
21             $_[0];
22             }
23              
24             sub squeeze_ws {
25             $_[0] =~ s/\s+/ /g;
26             $_[0];
27             }
28             }
29             Moose::Autobox->mixin_additional_role("SCALAR", "StringWithWhitespace");
30              
31              
32             =head1 NAME
33              
34             XS::Writer - Module to write some XS for you
35              
36             =head1 SYNOPSIS
37              
38             # As part of your build process...
39             use XS::Writer;
40              
41             my $writer = XS::Writer->new(
42             package => 'Some::Employee',
43              
44             # defines the employee struct
45             include => '#include "employee.h"',
46             );
47              
48             $writer->struct(<<'END');
49             typedef struct employee {
50             char * name;
51             double salary;
52             int id;
53             };
54             END
55              
56             # This will generate lib/Some/Employee_struct.xsi
57             # and lib/Some/Employee_struct.h
58             $writer->write_xs;
59              
60              
61             # Then in lib/Some/Employee.xs
62             #include "EXTERN.h"
63             #include "perl.h"
64             #include "XSUB.h"
65              
66             MODULE = Some::Employee PACKAGE = Some::Employee
67              
68             INCLUDE: Employee_struct.xsi
69              
70             ...any other XS you like...
71              
72              
73             # You must add this to lib/Some/typemap
74             TYPEMAP
75             Some::Employee T_PTROBJ
76              
77              
78             # And finally in lib/Some/Employee.pm
79             package Some::Employee;
80              
81             our $VERSION = 1.23;
82              
83             use XSLoader;
84             XSLoader::load __PACKAGE__, $VERSION;
85              
86              
87             # And you will be able to do
88             use Some::Employee;
89              
90             my $employee = Some::Employee->new;
91             $employee->name("Yarrow Hock");
92              
93              
94             =head1 DESCRIPTION
95              
96             I went nuts trying to figure out how to map structs into perl. I finally
97             figured it out and I never want anyone else to have to go through that.
98             I also wanted the process to remain transparent, many of the XS writing
99             modules are themselves almost as complicated as XS itself.
100              
101             This module helps you write XS by taking care of some of the rote things
102             for you. Right now it just makes structs available as objects, writing a
103             constructor and accessors. It's designed to be fairly transparent but
104             you still need to understand some XS.
105              
106             The instructions are meant for Module::Build. Adapt as necessary for
107             MakeMaker.
108              
109              
110             =head1 Example
111              
112             See F<t/Some-Employee> in the source tree for an example.
113              
114              
115             =head1 Stability
116              
117             It's not. I'm writing this to fit my own needs and it's likely to change
118             as my knowledge of XS changes. Also the XS it generates probably isn't the
119             best in the universe. Patches welcome.
120              
121              
122             =head1 Methods
123              
124             =head3 new
125              
126             my $writer = XS::Writer->new( %args );
127              
128             Setup a new writer. Arguments are...
129              
130             package (required) The package to write your XS into.
131             xs_file (optional) Where to write the XS file. Defaults to
132             lib/Your/Package_struct.xs
133             include (optional) Any extra code to include
134              
135             =cut
136              
137             has 'package',
138             is => 'rw',
139             required => 1
140             ;
141             has 'xs_type',
142             is => 'rw',
143             lazy => 1,
144             default => sub {
145             my $self = shift;
146             my $type = $self->package;
147             $type =~ s/::/__/g;
148             return $type;
149             }
150             ;
151             has 'xs_prefix',
152             is => 'rw',
153             lazy => 1,
154             default => sub {
155             my $self = shift;
156             return $self->xs_type . "_";
157             }
158             ;
159             has 'xs_file',
160             is => 'rw',
161             lazy => 1,
162             default => sub {
163             my $self = shift;
164             my $file = $self->package;
165             $file =~ s{::}{/}g;
166             return "lib/${file}_struct.xsi";
167             }
168             ;
169             has 'header_file',
170             is => 'rw',
171             lazy => 1,
172             default => sub {
173             my $self = shift;
174             my $header_file = basename($self->xs_file);
175             $header_file =~ s{\.xsi}{.h};
176             return $header_file;
177             }
178             ;
179             has 'include',
180             is => 'rw',
181             default => '',
182             ;
183             has 'struct_type',
184             is => 'rw'
185             ;
186             has 'struct_elements' =>
187             is => 'rw',
188             isa => 'HashRef'
189             ;
190             has 'struct_constructor' =>
191             is => 'rw',
192             lazy => 1,
193             default => sub {
194             my $self = shift;
195             return "(malloc(sizeof(@{[ $self->struct_type ]})))";
196             },
197             ;
198             has 'type_accessors' =>
199             is => 'rw',
200             isa => 'HashRef',
201             default => sub { {} },
202             ;
203              
204              
205             sub new {
206             my $class = shift;
207             my $self = $class->SUPER::new(@_);
208              
209             $self->type_accessor(int => <<'END');
210             $type
211             $accessor( $class self, ... )
212             CODE:
213             if( items > 1 )
214             self->$key = SvIV(ST(1));
215             RETVAL = self->$key;
216             OUTPUT:
217             RETVAL
218             END
219              
220             $self->type_accessor("char *" => <<'END');
221             $type
222             $accessor( $class self, ... )
223             CODE:
224             if( items > 1 )
225             self->$key = SvPV_nolen(ST(1));
226             RETVAL = self->$key;
227             OUTPUT:
228             RETVAL
229             END
230              
231             $self->type_accessor(double => <<'END');
232             $type
233             $accessor( $class self, ... )
234             CODE:
235             if( items > 1 )
236             self->$key = SvNV(ST(1));
237             RETVAL = self->$key;
238             OUTPUT:
239             RETVAL
240             END
241              
242             return $self;
243             }
244              
245              
246             =head3 struct
247              
248             $writer->struct($typedef);
249              
250             The typedef for the struct you'd like to write a class around.
251              
252             The C parser isn't too sophisticated.
253              
254             =cut
255              
256             sub struct {
257             my $self = shift;
258             my $typedef = shift;
259              
260             # Cleanup
261             $typedef =~ s{/\* .* \*/}{}g; # strip C comments
262             $typedef =~ s{//.*}{}g; # strip C++ comments
263             $typedef->strip_ws;
264              
265             $typedef =~ s/^typedef\s+//; # optional "typedef"
266             $typedef =~ s/^struct\s+(\w+)//; # struct type
267             my $type = $1;
268              
269             croak "Can't figure out the type" unless $type;
270              
271             # Get the braces out of the way.
272             $typedef =~ s/.*?{\s+//;
273             $typedef =~ s/\s+}.*?//;
274              
275             # All we should have left is "type key;"
276             my %elements = map {
277             /^(.*?)\s*(\w+)$/ ?
278             ($2 => $1) : ();
279             }
280             map { $_->strip_ws; $_->squeeze_ws }
281             split /;/, $typedef;
282              
283             croak "Didn't see any elements in $type" unless keys %elements;
284              
285             $self->struct_type($type);
286             $self->struct_elements(\%elements);
287             }
288              
289              
290             =head3 type_accessor
291              
292             $writer->type_accessor($type, $xs);
293              
294             XS::Writer will deal with simple types, but you will have to supply
295             code for anything beyond that.
296              
297             Here's an example for an accessor to elements with the 'double' type.
298              
299             $writer->type_accessor('double', <<'END_XS');
300             $type
301             $accessor( $class self, ... )
302             CODE:
303             if( items > 1 ) /* setting */
304             self->$key = SvNV(ST(1));
305              
306             RETVAL = self->$key;
307             OUTPUT:
308             RETVAL
309             END_XS
310              
311             Variables should be used in place of hard coding.
312              
313             $type same as the $type you gave
314             $accessor name of the accessor function
315             $class type of the struct
316             $key the element on the struct being accessed
317              
318             =cut
319              
320             sub type_accessor {
321             my $self = shift;
322             my($type, $xs) = @_;
323              
324             my $package = $self->package;
325              
326             $xs =~ s{\$type} {$type}g;
327             $xs =~ s{\$class}{$package}g;
328              
329             $self->type_accessors->{$type} = $xs;
330             }
331              
332             =head3 make_xs
333              
334             my $xs = $self->make_xs;
335              
336             Generates the XS code.
337              
338             =cut
339              
340             sub make_xs_header {
341             my $self = shift;
342              
343             my $xs = <<END;
344             # Generated by XS::Writer $VERSION
345              
346             #include "EXTERN.h"
347             #include "perl.h"
348             #include "XSUB.h"
349              
350             #include "@{[ $self->header_file ]}"
351              
352             MODULE = @{[ $self->package ]} PACKAGE = @{[ $self->package ]} PREFIX = @{[ $self->xs_prefix ]}
353              
354             @{[ $self->package ]}
355             @{[ $self->xs_type ]}_new( char* CLASS )
356             CODE:
357             RETVAL = (@{[ $self->struct_constructor ]});
358             if( RETVAL == NULL ) {
359             warn( "unable to create new @{[ $self->package ]}" );
360             }
361             OUTPUT:
362             RETVAL
363              
364              
365             void
366             @{[ $self->xs_type ]}_free( @{[ $self->package ]} self )
367             CODE:
368             free(self);
369             END
370              
371             return $xs;
372             }
373              
374              
375             sub make_xs_accessors {
376             my $self = shift;
377              
378             my $xs = '';
379              
380             my $elements = $self->struct_elements;
381             my $accessors = $self->type_accessors;
382             my $xs_type = $self->xs_type;
383             for my $key (sort { lc $a cmp lc $b } keys %$elements) {
384             my $type = $elements->{$key};
385              
386             my $accessor = $accessors->{$type}
387             or croak "No accessor for type $type";
388             $accessor =~ s/\$accessor/${xs_type}_${key}/g;
389             $accessor =~ s/\$key/$key/g;
390              
391             $xs .= $accessor;
392             $xs .= "\n\n";
393             }
394              
395             return $xs;
396             }
397              
398              
399             sub make_xs {
400             my $self = shift;
401              
402             return $self->make_xs_header
403             . "\n\n"
404             . $self->make_xs_accessors;
405             }
406              
407              
408             =head3 write_xs
409              
410             $writer->write_xs;
411              
412             Writes the XS to $writer->xs_file.
413              
414             =cut
415              
416             sub write_xs {
417             my $self = shift;
418            
419             $self->write_xs_file;
420             $self->write_header;
421             }
422              
423             sub write_xs_file {
424             my $self = shift;
425            
426             my $fh = $self->open_file(">", $self->xs_file);
427             print $fh $self->make_xs;
428             }
429              
430             sub write_header {
431             my $self = shift;
432            
433             my $fh = $self->open_file(">", $self->header_file);
434             print $fh <<"END";
435             /* Generated by XS::Writer $XS::Writer::VERSION */
436              
437             @{[ $self->include ]}
438              
439             typedef @{[ $self->struct_type ]} * @{[ $self->xs_type ]};
440             END
441              
442             }
443              
444             sub open_file {
445             my $self = shift;
446             my($mode, $file) = @_;
447            
448             my $dir = dirname($file);
449             mkpath $dir unless -d $dir;
450            
451             open my $fh, $mode, $file
452             or die "Can't write to $file: $!";
453            
454             return $fh;
455             }
456              
457              
458             =head1 AUTHOR
459              
460             Michael G Schwern E<lt>schwern@pobox.comE<gt>
461              
462              
463             =head1 LICENSE
464              
465             Copyright 2008 by Michael G Schwern E<lt>schwern@pobox.comE<gt>.
466              
467             This program is free software; you can redistribute it and/or
468             modify it under the same terms as Perl itself.
469              
470             See F<http://dev.perl.org/licenses>
471              
472              
473             =head1 THANKS
474              
475             Thanks to...
476              
477             Tom Heady for answering my cry for XS help and showing me how
478             to do struct accessors.
479              
480             Simon Cozens for "Embedding and Extending Perl"
481              
482              
483             =head1 SEE ALSO
484              
485             L<Inline::Struct>, L<ExtUtils::XSBuilder>, L<perlxs>
486              
487             =cut
488              
489             1;