1 package ExtUtils::Typemaps;
8 our $Proto_Regexp = "[" . quotemeta('\$%&*@;[]') . "]";
10 require ExtUtils::Typemaps::InputMap;
11 require ExtUtils::Typemaps::OutputMap;
12 require ExtUtils::Typemaps::Type;
16 ExtUtils::Typemaps - Read/Write/Modify Perl/XS typemap files
21 my $typemap = ExtUtils::Typemaps->new(file => 'typemap');
22 # alternatively create an in-memory typemap
23 # $typemap = ExtUtils::Typemaps->new();
24 # alternatively create an in-memory typemap by parsing a string
25 # $typemap = ExtUtils::Typemaps->new(string => $sometypemap);
28 $typemap->add_typemap(ctype => 'NV', xstype => 'T_NV');
29 $typemap->add_inputmap (xstype => 'T_NV', code => '$var = ($type)SvNV($arg);');
30 $typemap->add_outputmap(xstype => 'T_NV', code => 'sv_setnv($arg, (NV)$var);');
31 $typemap->add_string(string => $typemapstring); # will be parsed and merged
33 # remove a mapping (same for remove_typemap and remove_outputmap...)
34 $typemap->remove_inputmap(xstype => 'SomeType');
36 # save a typemap to a file
37 $typemap->write(file => 'anotherfile.map');
39 # merge the other typemap into this one
40 $typemap->merge(typemap => $another_typemap);
44 This module can read, modify, create and write Perl XS typemap files. If you don't know
45 what a typemap is, please confer the L<perlxstut> and L<perlxs> manuals.
47 The module is not entirely round-trip safe: For example it currently simply strips all comments.
48 The order of entries in the maps is, however, preserved.
50 We check for duplicate entries in the typemap, but do not check for missing
51 C<TYPEMAP> entries for C<INPUTMAP> or C<OUTPUTMAP> entries since these might be hidden
52 in a different typemap.
60 Returns a new typemap object. Takes an optional C<file> parameter.
61 If set, the given file will be read. If the file doesn't exist, an empty typemap
64 Alternatively, if the C<string> parameter is given, the supplied
65 string will be parsed instead of a file.
73 if (defined $args{file} and defined $args{string}) {
74 croak("Cannot handle both 'file' and 'string' arguments to constructor");
80 typemap_section => [],
92 if (defined $self->{string}) {
93 $self->_parse(\($self->{string}));
94 delete $self->{string};
96 elsif (defined $self->{file} and -e $self->{file}) {
97 open my $fh, '<', $self->{file}
98 or die "Cannot open typemap file '"
99 . $self->{file} . "' for reading: $!";
102 $self->_parse(\$string, $self->{file});
108 Get/set the file that the typemap is written to when the
109 C<write> method is called.
114 $_[0]->{file} = $_[1] if @_ > 1;
120 Add a C<TYPEMAP> entry to the typemap.
122 Required named arguments: The C<ctype> (e.g. C<ctype =E<gt> 'double'>)
123 and the C<xstype> (e.g. C<xstype =E<gt> 'T_NV'>).
125 Optional named arguments: C<replace =E<gt> 1> forces removal/replacement of
126 existing C<TYPEMAP> entries of the same C<ctype>.
128 As an alternative to the named parameters usage, you may pass in
129 an C<ExtUtils::Typemaps::Type> object, a copy of which will be
130 added to the typemap.
140 $type = $orig->new(@_);
144 my $ctype = $args{ctype};
145 croak("Need ctype argument") if not defined $ctype;
146 my $xstype = $args{xstype};
147 croak("Need xstype argument") if not defined $xstype;
149 $type = ExtUtils::Typemaps::Type->new(
151 'prototype' => $args{'prototype'},
154 $replace = $args{replace};
158 $self->remove_typemap(ctype => $type->ctype);
160 $self->validate(typemap_xstype => $type->xstype, ctype => $type->ctype);
162 push @{$self->{typemap_section}}, $type;
168 Add an C<INPUT> entry to the typemap.
170 Required named arguments:
171 The C<xstype> (e.g. C<xstype =E<gt> 'T_NV'>)
172 and the C<code> to associate with it for input.
174 Optional named arguments: C<replace =E<gt> 1> forces removal/replacement of
175 existing C<INPUT> entries of the same C<xstype>.
177 You may pass in a single C<ExtUtils::Typemaps::InputMap> object instead,
178 a copy of which will be added to the typemap.
188 $input = $orig->new(@_);
192 my $xstype = $args{xstype};
193 croak("Need xstype argument") if not defined $xstype;
194 my $code = $args{code};
195 croak("Need code argument") if not defined $code;
197 $input = ExtUtils::Typemaps::InputMap->new(
201 $replace = $args{replace};
204 $self->remove_inputmap(xstype => $input->xstype);
206 $self->validate(inputmap_xstype => $input->xstype);
208 push @{$self->{input_section}}, $input;
214 Add an C<OUTPUT> entry to the typemap.
215 Works exactly the same as C<add_inputmap>.
225 $output = $orig->new(@_);
229 my $xstype = $args{xstype};
230 croak("Need xstype argument") if not defined $xstype;
231 my $code = $args{code};
232 croak("Need code argument") if not defined $code;
234 $output = ExtUtils::Typemaps::OutputMap->new(
238 $replace = $args{replace};
241 $self->remove_outputmap(xstype => $output->xstype);
243 $self->validate(outputmap_xstype => $output->xstype);
245 push @{$self->{output_section}}, $output;
251 Parses a string as a typemap and merge it into the typemap object.
253 Required named argument: C<string> to specify the string to parse.
260 croak("Need 'string' argument") if not defined $args{string};
262 # no, this is not elegant.
263 my $other = ExtUtils::Typemaps->new(string => $args{string});
264 $self->merge(typemap => $other);
267 =head2 remove_typemap
269 Removes a C<TYPEMAP> entry from the typemap.
271 Required named argument: C<ctype> to specify the entry to remove from the typemap.
273 Alternatively, you may pass a single C<ExtUtils::Typemaps::Type> object.
282 $ctype = $args{ctype};
283 croak("Need ctype argument") if not defined $ctype;
284 $ctype = _tidy_type($ctype);
287 $ctype = $_[0]->tidy_ctype;
290 return $self->_remove($ctype, 'tidy_ctype', $self->{typemap_section});
293 =head2 remove_inputmap
295 Removes an C<INPUT> entry from the typemap.
297 Required named argument: C<xstype> to specify the entry to remove from the typemap.
299 Alternatively, you may pass a single C<ExtUtils::Typemaps::InputMap> object.
303 sub remove_inputmap {
308 $xstype = $args{xstype};
309 croak("Need xstype argument") if not defined $xstype;
312 $xstype = $_[0]->xstype;
315 return $self->_remove($xstype, 'xstype', $self->{input_section});
318 =head2 remove_inputmap
320 Removes an C<OUTPUT> entry from the typemap.
322 Required named argument: C<xstype> to specify the entry to remove from the typemap.
324 Alternatively, you may pass a single C<ExtUtils::Typemaps::OutputMap> object.
328 sub remove_outputmap {
333 $xstype = $args{xstype};
334 croak("Need xstype argument") if not defined $xstype;
337 $xstype = $_[0]->xstype;
340 return $self->_remove($xstype, 'xstype', $self->{output_section});
350 foreach my $map (@$array) {
351 last if $map->$method() eq $rm;
354 if ($index < @$array) {
355 splice(@$array, $index, 1);
363 Fetches an entry of the TYPEMAP section of the typemap.
365 Mandatory named arguments: The C<ctype> of the entry.
367 Returns the C<ExtUtils::Typemaps::Type>
368 object for the entry if found.
375 my $ctype = $args{ctype};
376 croak("Need ctype argument") if not defined $ctype;
377 $ctype = _tidy_type($ctype);
379 foreach my $map (@{$self->{typemap_section}}) {
380 return $map if $map->tidy_ctype eq $ctype;
387 Fetches an entry of the INPUT section of the
390 Mandatory named arguments: The C<xstype> of the
393 Returns the C<ExtUtils::Typemaps::InputMap>
394 object for the entry if found.
401 my $xstype = $args{xstype};
402 croak("Need xstype argument") if not defined $xstype;
404 foreach my $map (@{$self->{input_section}}) {
405 return $map if $map->xstype eq $xstype;
412 Fetches an entry of the OUTPUT section of the
415 Mandatory named arguments: The C<xstype> of the
418 Returns the C<ExtUtils::Typemaps::InputMap>
419 object for the entry if found.
426 my $xstype = $args{xstype};
427 croak("Need xstype argument") if not defined $xstype;
429 foreach my $map (@{$self->{output_section}}) {
430 return $map if $map->xstype eq $xstype;
437 Write the typemap to a file. Optionally takes a C<file> argument. If given, the
438 typemap will be written to the specified file. If not, the typemap is written
439 to the currently stored file name (see C<-E<gt>file> above, this defaults to the file
440 it was read from if any).
447 my $file = defined $args{file} ? $args{file} : $self->file();
448 croak("write() needs a file argument (or set the file name of the typemap using the 'file' method)")
449 if not defined $file;
451 open my $fh, '>', $file
452 or die "Cannot open typemap file '$file' for writing: $!";
453 print $fh $self->as_string();
459 Generates and returns the string form of the typemap.
465 my $typemap = $self->{typemap_section};
467 push @code, "TYPEMAP\n";
468 foreach my $entry (@$typemap) {
470 # /^(.*?\S)\s+(\S+)\s*($Proto_Regexp*)$/o
471 push @code, $entry->ctype . "\t" . $entry->xstype
472 . ($entry->proto ne '' ? "\t".$entry->proto : '') . "\n";
475 my $input = $self->{input_section};
477 push @code, "\nINPUT\n";
478 foreach my $entry (@$input) {
479 push @code, $entry->xstype, "\n", $entry->code, "\n";
483 my $output = $self->{output_section};
485 push @code, "\nOUTPUT\n";
486 foreach my $entry (@$output) {
487 push @code, $entry->xstype, "\n", $entry->code, "\n";
490 return join '', @code;
495 Merges a given typemap into the object. Note that a failed merge
496 operation leaves the object in an inconsistent state so clone if necessary.
498 Mandatory named argument: C<typemap =E<gt> $another_typemap>
500 Optional argument: C<replace =E<gt> 1> to force replacement
501 of existing typemap entries without warning.
508 my $typemap = $args{typemap};
509 croak("Need ExtUtils::Typemaps as argument")
510 if not ref $typemap or not $typemap->isa('ExtUtils::Typemaps');
512 my $replace = $args{replace};
514 # FIXME breaking encapsulation. Add accessor code.
516 foreach my $entry (@{$typemap->{typemap_section}}) {
517 $self->add_typemap( $entry );
520 foreach my $entry (@{$typemap->{input_section}}) {
521 $self->add_inputmap( $entry );
524 foreach my $entry (@{$typemap->{output_section}}) {
525 $self->add_outputmap( $entry );
531 # Note: This is really inefficient. One could keep a hash to start with.
538 $xstypes{$args{typemap_xstype}}++ if defined $args{typemap_xstype};
539 $ctypes{$args{ctype}}++ if defined $args{ctype};
541 foreach my $map (@{$self->{typemap_section}}) {
542 my $ctype = $map->tidy_ctype;
543 croak("Multiple definition of ctype '$ctype' in TYPEMAP section")
544 if exists $ctypes{$ctype};
545 my $xstype = $map->xstype;
546 # TODO check this: We shouldn't complain about reusing XS types in TYPEMAP.
547 #croak("Multiple definition of xstype '$xstype' in TYPEMAP section")
548 # if exists $xstypes{$xstype};
554 $xstypes{$args{inputmap_xstype}}++ if defined $args{inputmap_xstype};
555 foreach my $map (@{$self->{input_section}}) {
556 my $xstype = $map->xstype;
557 croak("Multiple definition of xstype '$xstype' in INPUTMAP section")
558 if exists $xstypes{$xstype};
563 $xstypes{$args{outputmap_xstype}}++ if defined $args{outputmap_xstype};
564 foreach my $map (@{$self->{output_section}}) {
565 my $xstype = $map->xstype;
566 croak("Multiple definition of xstype '$xstype' in OUTPUTMAP section")
567 if exists $xstypes{$xstype};
576 my $stringref = shift;
577 my $filename = shift;
578 $filename = '<string>' if not defined $filename;
580 # TODO comments should round-trip, currently ignoring
581 # TODO order of sections, multiple sections of same type
582 # Heavily influenced by ExtUtils::ParseXS
583 my $section = 'typemap';
586 my $current = \$junk;
590 while ($$stringref =~ /^(.*)$/gcm) {
600 elsif (/^OUTPUT\s*$/) {
605 elsif (/^TYPEMAP\s*$/) {
606 $section = 'typemap';
611 if ($section eq 'typemap') {
614 next if /^#/ or /^$/;
615 my($type, $kind, $proto) = /^(.*?\S)\s+(\S+)\s*($Proto_Regexp*)$/o
616 or warn("Warning: File '$filename' Line $lineno '$line' TYPEMAP entry needs 2 or 3 columns\n"),
618 #$proto = '' if not $proto;
619 # prototype defaults to '$'
620 #$proto = '$' unless $proto;
621 #warn("Warning: File '$filename' Line $lineno '$line' Invalid prototype '$proto'\n")
622 # unless _valid_proto_string($proto);
623 push @typemap_expr, ExtUtils::Typemaps::Type->new(
624 xstype => $kind, proto => $proto, ctype => $type
627 $$current .= $$current eq '' ? $_ : "\n".$_;
630 } elsif ($section eq 'input') {
632 push @input_expr, {xstype => $_, code => ''};
633 $current = \$input_expr[-1]{code};
634 } else { # output section
636 push @output_expr, {xstype => $_, code => ''};
637 $current = \$output_expr[-1]{code};
642 $self->{typemap_section} = \@typemap_expr;
643 $self->{input_section} = [ map {ExtUtils::Typemaps::InputMap->new(%$_) } @input_expr ];
644 $self->{output_section} = [ map {ExtUtils::Typemaps::OutputMap->new(%$_) } @output_expr ];
646 return $self->validate();
649 # taken from ExtUtils::ParseXS
653 # rationalise any '*' by joining them into bunches and removing whitespace
657 # trim leading & trailing whitespace
660 # change multiple whitespace into a single space
667 # taken from ExtUtils::ParseXS
668 sub _valid_proto_string {
670 if ($string =~ /^$Proto_Regexp+$/o) {
677 # taken from ExtUtils::ParseXS (C_string)
678 sub _escape_backslashes {
680 $string =~ s[\\][\\\\]g;
686 Not as well tested as I'd like it to be.
688 Inherits some evil code from C<ExtUtils::ParseXS>.
690 Adding more typemaps incurs an O(n) validation penalty
691 that could be optimized with a hash.
695 The parser is heavily inspired from the one in L<ExtUtils::ParseXS>.
697 For details on typemaps: L<perlxstut>, L<perlxs>.
701 Steffen Mueller C<<smueller@cpan.org>>
703 =head1 COPYRIGHT & LICENSE
705 Copyright 2009-2010 Steffen Mueller
707 This program is free software; you can redistribute it and/or
708 modify it under the same terms as Perl itself.