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 => [],
95 if (defined $self->{string}) {
96 $self->_parse(\($self->{string}));
97 delete $self->{string};
99 elsif (defined $self->{file} and -e $self->{file}) {
100 open my $fh, '<', $self->{file}
101 or die "Cannot open typemap file '"
102 . $self->{file} . "' for reading: $!";
105 $self->_parse(\$string, $self->{file});
111 Get/set the file that the typemap is written to when the
112 C<write> method is called.
117 $_[0]->{file} = $_[1] if @_ > 1;
123 Add a C<TYPEMAP> entry to the typemap.
125 Required named arguments: The C<ctype> (e.g. C<ctype =E<gt> 'double'>)
126 and the C<xstype> (e.g. C<xstype =E<gt> 'T_NV'>).
128 Optional named arguments: C<replace =E<gt> 1> forces removal/replacement of
129 existing C<TYPEMAP> entries of the same C<ctype>.
131 As an alternative to the named parameters usage, you may pass in
132 an C<ExtUtils::Typemaps::Type> object, a copy of which will be
133 added to the typemap.
143 $type = $orig->new(@_);
147 my $ctype = $args{ctype};
148 croak("Need ctype argument") if not defined $ctype;
149 my $xstype = $args{xstype};
150 croak("Need xstype argument") if not defined $xstype;
152 $type = ExtUtils::Typemaps::Type->new(
154 'prototype' => $args{'prototype'},
157 $replace = $args{replace};
161 $self->remove_typemap(ctype => $type->ctype);
163 $self->validate(typemap_xstype => $type->xstype, ctype => $type->ctype);
167 push @{$self->{typemap_section}}, $type;
168 # remember type for lookup, too.
169 $self->{typemap_lookup}{$type->tidy_ctype} = $#{$self->{typemap_section}};
176 Add an C<INPUT> entry to the typemap.
178 Required named arguments:
179 The C<xstype> (e.g. C<xstype =E<gt> 'T_NV'>)
180 and the C<code> to associate with it for input.
182 Optional named arguments: C<replace =E<gt> 1> forces removal/replacement of
183 existing C<INPUT> entries of the same C<xstype>.
185 You may pass in a single C<ExtUtils::Typemaps::InputMap> object instead,
186 a copy of which will be added to the typemap.
196 $input = $orig->new(@_);
200 my $xstype = $args{xstype};
201 croak("Need xstype argument") if not defined $xstype;
202 my $code = $args{code};
203 croak("Need code argument") if not defined $code;
205 $input = ExtUtils::Typemaps::InputMap->new(
209 $replace = $args{replace};
212 $self->remove_inputmap(xstype => $input->xstype);
214 $self->validate(inputmap_xstype => $input->xstype);
218 push @{$self->{input_section}}, $input;
219 # remember type for lookup, too.
220 $self->{input_lookup}{$input->xstype} = $#{$self->{input_section}};
227 Add an C<OUTPUT> entry to the typemap.
228 Works exactly the same as C<add_inputmap>.
238 $output = $orig->new(@_);
242 my $xstype = $args{xstype};
243 croak("Need xstype argument") if not defined $xstype;
244 my $code = $args{code};
245 croak("Need code argument") if not defined $code;
247 $output = ExtUtils::Typemaps::OutputMap->new(
251 $replace = $args{replace};
254 $self->remove_outputmap(xstype => $output->xstype);
256 $self->validate(outputmap_xstype => $output->xstype);
260 push @{$self->{output_section}}, $output;
261 # remember type for lookup, too.
262 $self->{output_lookup}{$output->xstype} = $#{$self->{output_section}};
269 Parses a string as a typemap and merge it into the typemap object.
271 Required named argument: C<string> to specify the string to parse.
278 croak("Need 'string' argument") if not defined $args{string};
280 # no, this is not elegant.
281 my $other = ExtUtils::Typemaps->new(string => $args{string});
282 $self->merge(typemap => $other);
285 =head2 remove_typemap
287 Removes a C<TYPEMAP> entry from the typemap.
289 Required named argument: C<ctype> to specify the entry to remove from the typemap.
291 Alternatively, you may pass a single C<ExtUtils::Typemaps::Type> object.
300 $ctype = $args{ctype};
301 croak("Need ctype argument") if not defined $ctype;
302 $ctype = _tidy_type($ctype);
305 $ctype = $_[0]->tidy_ctype;
308 return $self->_remove($ctype, 'tidy_ctype', $self->{typemap_section}, $self->{typemap_lookup});
311 =head2 remove_inputmap
313 Removes an C<INPUT> entry from the typemap.
315 Required named argument: C<xstype> to specify the entry to remove from the typemap.
317 Alternatively, you may pass a single C<ExtUtils::Typemaps::InputMap> object.
321 sub remove_inputmap {
326 $xstype = $args{xstype};
327 croak("Need xstype argument") if not defined $xstype;
330 $xstype = $_[0]->xstype;
333 return $self->_remove($xstype, 'xstype', $self->{input_section}, $self->{input_lookup});
336 =head2 remove_inputmap
338 Removes an C<OUTPUT> entry from the typemap.
340 Required named argument: C<xstype> to specify the entry to remove from the typemap.
342 Alternatively, you may pass a single C<ExtUtils::Typemaps::OutputMap> object.
346 sub remove_outputmap {
351 $xstype = $args{xstype};
352 croak("Need xstype argument") if not defined $xstype;
355 $xstype = $_[0]->xstype;
358 return $self->_remove($xstype, 'xstype', $self->{output_section}, $self->{output_lookup});
369 my $index = $lookup->{$rm};
370 return() if not defined $index;
371 splice(@$array, $index, 1);
372 foreach my $key (keys %$lookup) {
373 if ($lookup->{$key} > $index) {
380 foreach my $map (@$array) {
381 last if $map->$method() eq $rm;
384 if ($index < @$array) {
385 splice(@$array, $index, 1);
394 Fetches an entry of the TYPEMAP section of the typemap.
396 Mandatory named arguments: The C<ctype> of the entry.
398 Returns the C<ExtUtils::Typemaps::Type>
399 object for the entry if found.
406 my $ctype = $args{ctype};
407 croak("Need ctype argument") if not defined $ctype;
408 $ctype = _tidy_type($ctype);
410 my $index = $self->{typemap_lookup}{$ctype};
411 return() if not defined $index;
412 return $self->{typemap_section}[$index];
417 Fetches an entry of the INPUT section of the
420 Mandatory named arguments: The C<xstype> of the
423 Returns the C<ExtUtils::Typemaps::InputMap>
424 object for the entry if found.
431 my $xstype = $args{xstype};
432 croak("Need xstype argument") if not defined $xstype;
434 my $index = $self->{input_lookup}{$xstype};
435 return() if not defined $index;
436 return $self->{input_section}[$index];
441 Fetches an entry of the OUTPUT section of the
444 Mandatory named arguments: The C<xstype> of the
447 Returns the C<ExtUtils::Typemaps::InputMap>
448 object for the entry if found.
455 my $xstype = $args{xstype};
456 croak("Need xstype argument") if not defined $xstype;
458 my $index = $self->{output_lookup}{$xstype};
459 return() if not defined $index;
460 return $self->{output_section}[$index];
465 Write the typemap to a file. Optionally takes a C<file> argument. If given, the
466 typemap will be written to the specified file. If not, the typemap is written
467 to the currently stored file name (see C<-E<gt>file> above, this defaults to the file
468 it was read from if any).
475 my $file = defined $args{file} ? $args{file} : $self->file();
476 croak("write() needs a file argument (or set the file name of the typemap using the 'file' method)")
477 if not defined $file;
479 open my $fh, '>', $file
480 or die "Cannot open typemap file '$file' for writing: $!";
481 print $fh $self->as_string();
487 Generates and returns the string form of the typemap.
493 my $typemap = $self->{typemap_section};
495 push @code, "TYPEMAP\n";
496 foreach my $entry (@$typemap) {
498 # /^(.*?\S)\s+(\S+)\s*($Proto_Regexp*)$/o
499 push @code, $entry->ctype . "\t" . $entry->xstype
500 . ($entry->proto ne '' ? "\t".$entry->proto : '') . "\n";
503 my $input = $self->{input_section};
505 push @code, "\nINPUT\n";
506 foreach my $entry (@$input) {
507 push @code, $entry->xstype, "\n", $entry->code, "\n";
511 my $output = $self->{output_section};
513 push @code, "\nOUTPUT\n";
514 foreach my $entry (@$output) {
515 push @code, $entry->xstype, "\n", $entry->code, "\n";
518 return join '', @code;
523 Merges a given typemap into the object. Note that a failed merge
524 operation leaves the object in an inconsistent state so clone if necessary.
526 Mandatory named argument: C<typemap =E<gt> $another_typemap>
528 Optional argument: C<replace =E<gt> 1> to force replacement
529 of existing typemap entries without warning.
536 my $typemap = $args{typemap};
537 croak("Need ExtUtils::Typemaps as argument")
538 if not ref $typemap or not $typemap->isa('ExtUtils::Typemaps');
540 my $replace = $args{replace};
542 # FIXME breaking encapsulation. Add accessor code.
544 foreach my $entry (@{$typemap->{typemap_section}}) {
545 $self->add_typemap( $entry );
548 foreach my $entry (@{$typemap->{input_section}}) {
549 $self->add_inputmap( $entry );
552 foreach my $entry (@{$typemap->{output_section}}) {
553 $self->add_outputmap( $entry );
560 # make sure that the provided types wouldn't collide with what's
561 # in the object already.
566 if ( exists $args{ctype}
567 and exists $self->{typemap_lookup}{_tidy_type($args{ctype})} )
569 croak("Multiple definition of ctype '$args{ctype}' in TYPEMAP section");
572 if ( exists $args{inputmap_xstype}
573 and exists $self->{input_lookup}{$args{inputmap_xstype}} )
575 croak("Multiple definition of xstype '$args{inputmap_xstype}' in INPUTMAP section");
578 if ( exists $args{outputmap_xstype}
579 and exists $self->{output_lookup}{$args{outputmap_xstype}} )
581 croak("Multiple definition of xstype '$args{outputmap_xstype}' in OUTPUTMAP section");
589 my $stringref = shift;
590 my $filename = shift;
591 $filename = '<string>' if not defined $filename;
593 # TODO comments should round-trip, currently ignoring
594 # TODO order of sections, multiple sections of same type
595 # Heavily influenced by ExtUtils::ParseXS
596 my $section = 'typemap';
599 my $current = \$junk;
602 while ($$stringref =~ /^(.*)$/gcm) {
612 elsif (/^OUTPUT\s*$/) {
617 elsif (/^TYPEMAP\s*$/) {
618 $section = 'typemap';
623 if ($section eq 'typemap') {
626 next if /^#/ or /^$/;
627 my($type, $kind, $proto) = /^(.*?\S)\s+(\S+)\s*($Proto_Regexp*)$/o
628 or warn("Warning: File '$filename' Line $lineno '$line' TYPEMAP entry needs 2 or 3 columns\n"),
630 #$proto = '' if not $proto;
631 # prototype defaults to '$'
632 #$proto = '$' unless $proto;
633 #warn("Warning: File '$filename' Line $lineno '$line' Invalid prototype '$proto'\n")
634 # unless _valid_proto_string($proto);
636 ExtUtils::Typemaps::Type->new(
637 xstype => $kind, proto => $proto, ctype => $type
641 $$current .= $$current eq '' ? $_ : "\n".$_;
644 } elsif ($section eq 'input') {
646 push @input_expr, {xstype => $_, code => ''};
647 $current = \$input_expr[-1]{code};
648 } else { # output section
650 push @output_expr, {xstype => $_, code => ''};
651 $current = \$output_expr[-1]{code};
656 foreach my $inexpr (@input_expr) {
657 $self->add_inputmap( ExtUtils::Typemaps::InputMap->new(%$inexpr) );
659 foreach my $outexpr (@output_expr) {
660 $self->add_outputmap( ExtUtils::Typemaps::OutputMap->new(%$outexpr) );
666 # taken from ExtUtils::ParseXS
670 # rationalise any '*' by joining them into bunches and removing whitespace
674 # trim leading & trailing whitespace
677 # change multiple whitespace into a single space
684 # taken from ExtUtils::ParseXS
685 sub _valid_proto_string {
687 if ($string =~ /^$Proto_Regexp+$/o) {
694 # taken from ExtUtils::ParseXS (C_string)
695 sub _escape_backslashes {
697 $string =~ s[\\][\\\\]g;
703 Inherits some evil code from C<ExtUtils::ParseXS>.
707 The parser is heavily inspired from the one in L<ExtUtils::ParseXS>.
709 For details on typemaps: L<perlxstut>, L<perlxs>.
713 Steffen Mueller C<<smueller@cpan.org>>
715 =head1 COPYRIGHT & LICENSE
717 Copyright 2009-2011 Steffen Mueller
719 This program is free software; you can redistribute it and/or
720 modify it under the same terms as Perl itself.