blob: 17dace772bf3bc51694ea689da0457174d734f9f [file] [log] [blame]
# This should eventually become part of MakeMaker as ExtUtils::Mkconst2perl.
# Documentation for this is very skimpy at this point. Full documentation
# will be added to ExtUtils::Mkconst2perl when it is created.
package # Hide from PAUSE
ExtUtils::Myconst2perl;
use strict;
use Config;
use vars qw( @ISA @EXPORT @EXPORT_OK $VERSION );
BEGIN {
require Exporter;
push @ISA, 'Exporter';
@EXPORT= qw( &Myconst2perl );
@EXPORT_OK= qw( &ParseAttribs );
$VERSION= 1.00;
}
use Carp;
use File::Basename;
use ExtUtils::MakeMaker qw( neatvalue );
# Return the extension to use for a file of C++ source code:
sub _cc
{
# Some day, $Config{_cc} might be defined for us:
return $Config{_cc} if $Config{_cc};
return ".cxx"; # Seems to be the most widely accepted extension.
}
=item ParseAttribs
Parses user-firendly options into coder-firendly specifics.
=cut
sub ParseAttribs
{
# Usage: ParseAttribs( "Package::Name", \%opts, {opt=>\$var} );
my( $pkg, $hvAttr, $hvRequests )= @_;
my( $outfile, @perlfiles, %perlfilecodes, @cfiles, %cfilecodes );
my @importlist= @{$hvAttr->{IMPORT_LIST}};
my $perlcode= $hvAttr->{PERL_PE_CODE} ||
'last if /^\s*(bootstrap|XSLoader::load)\b/';
my $ccode= $hvAttr->{C_PE_CODE} ||
'last if m#/[/*]\s*CONSTS_DEFINED\b|^\s*MODULE\b#';
my $ifdef= $hvAttr->{IFDEF} || 0;
my $writeperl= !! $hvAttr->{WRITE_PERL};
my $export= !! $hvAttr->{DO_EXPORT};
my $importto= $hvAttr->{IMPORT_TO} || "_constants";
my $cplusplus= $hvAttr->{CPLUSPLUS};
$cplusplus= "" if ! defined $cplusplus;
my $object= "";
my $binary= "";
my $final= "";
my $norebuild= "";
my $subroutine= "";
my $base;
my %params= (
PERL_PE_CODE => \$perlcode,
PERL_FILE_LIST => \@perlfiles,
PERL_FILE_CODES => \%perlfilecodes,
PERL_FILES => sub { map {($_,$perlfilecodes{$_})} @perlfiles },
C_PE_CODE => \$ccode,
C_FILE_LIST => \@cfiles,
C_FILE_CODES => \%cfilecodes,
C_FILES => sub { map {($_,$cfilecodes{$_})} @cfiles },
DO_EXPORT => \$export,
IMPORT_TO => \$importto,
IMPORT_LIST => \@importlist,
SUBROUTINE => \$subroutine,
IFDEF => \$ifdef,
WRITE_PERL => \$writeperl,
CPLUSPLUS => \$cplusplus,
BASEFILENAME => \$base,
OUTFILE => \$outfile,
OBJECT => \$object,
BINARY => \$binary,
FINAL_PERL => \$final,
NO_REBUILD => \$norebuild,
);
{ my @err= grep {! defined $params{$_}} keys %$hvAttr;
carp "ExtUtils::Myconst2perl::ParseAttribs: ",
"Unsupported option(s) (@err).\n"
if @err;
}
$norebuild= $hvAttr->{NO_REBUILD} if exists $hvAttr->{NO_REBUILD};
my $module= ( split /::/, $pkg )[-1];
$base= "c".$module;
$base= $hvAttr->{BASEFILENAME} if exists $hvAttr->{BASEFILENAME};
my $ext= ! $cplusplus ? ($Config{_c}||".c")
: $cplusplus =~ /^[.]/ ? $cplusplus : _cc();
if( $writeperl ) {
$outfile= $base . "_pc" . $ext;
$object= $base . "_pc" . ($Config{_o}||$Config{obj_ext});
$object= $hvAttr->{OBJECT} if $hvAttr->{OBJECT};
$binary= $base . "_pc" . ($Config{_exe}||$Config{exe_ext});
$binary= $hvAttr->{BINARY} if $hvAttr->{BINARY};
$final= $base . ".pc";
$final= $hvAttr->{FINAL_PERL} if $hvAttr->{FINAL_PERL};
$subroutine= "main";
} elsif( $cplusplus ) {
$outfile= $base . $ext;
$object= $base . ($Config{_o}||$Config{obj_ext});
$object= $hvAttr->{OBJECT} if $hvAttr->{OBJECT};
$subroutine= "const2perl_" . $pkg;
$subroutine =~ s/\W/_/g;
} else {
$outfile= $base . ".h";
}
$outfile= $hvAttr->{OUTFILE} if $hvAttr->{OUTFILE};
if( $hvAttr->{PERL_FILES} ) {
carp "ExtUtils::Myconst2perl: PERL_FILES option not allowed ",
"with PERL_FILE_LIST nor PERL_FILE_CODES.\n"
if $hvAttr->{PERL_FILE_LIST} || $hvAttr->{PERL_FILE_CODES};
%perlfilecodes= @{$hvAttr->{PERL_FILES}};
my $odd= 0;
@perlfiles= grep {$odd= !$odd} @{$hvAttr->{PERL_FILES}};
} else {
if( $hvAttr->{PERL_FILE_LIST} ) {
@perlfiles= @{$hvAttr->{PERL_FILE_LIST}};
} elsif( $hvAttr->{PERL_FILE_CODES} ) {
@perlfiles= keys %{$hvAttr->{PERL_FILE_CODES}};
} else {
@perlfiles= ( "$module.pm" );
}
%perlfilecodes= %{$hvAttr->{PERL_FILE_CODES}}
if $hvAttr->{PERL_FILE_CODES};
}
for my $file ( @perlfiles ) {
$perlfilecodes{$file}= $perlcode if ! $perlfilecodes{$file};
}
if( ! $subroutine ) {
; # Don't process any C source code files.
} elsif( $hvAttr->{C_FILES} ) {
carp "ExtUtils::Myconst2perl: C_FILES option not allowed ",
"with C_FILE_LIST nor C_FILE_CODES.\n"
if $hvAttr->{C_FILE_LIST} || $hvAttr->{C_FILE_CODES};
%cfilecodes= @{$hvAttr->{C_FILES}};
my $odd= 0;
@cfiles= grep {$odd= !$odd} @{$hvAttr->{C_FILES}};
} else {
if( $hvAttr->{C_FILE_LIST} ) {
@cfiles= @{$hvAttr->{C_FILE_LIST}};
} elsif( $hvAttr->{C_FILE_CODES} ) {
@cfiles= keys %{$hvAttr->{C_FILE_CODES}};
} elsif( $writeperl || $cplusplus ) {
@cfiles= ( "$module.xs" );
}
%cfilecodes= %{$hvAttr->{C_FILE_CODES}} if $hvAttr->{C_FILE_CODES};
}
for my $file ( @cfiles ) {
$cfilecodes{$file}= $ccode if ! $cfilecodes{$file};
}
for my $key ( keys %$hvRequests ) {
if( ! $params{$key} ) {
carp "ExtUtils::Myconst2perl::ParseAttribs: ",
"Unsupported output ($key).\n";
} elsif( "SCALAR" eq ref( $params{$key} ) ) {
${$hvRequests->{$key}}= ${$params{$key}};
} elsif( "ARRAY" eq ref( $params{$key} ) ) {
@{$hvRequests->{$key}}= @{$params{$key}};
} elsif( "HASH" eq ref( $params{$key} ) ) {
%{$hvRequests->{$key}}= %{$params{$key}};
} elsif( "CODE" eq ref( $params{$key} ) ) {
@{$hvRequests->{$key}}= &{$params{$key}};
} else {
die "Impossible value in \$params{$key}";
}
}
}
=item Myconst2perl
Generates a file used to implement C constants as "constant subroutines" in
a Perl module.
Extracts a list of constants from a module's export list by C<eval>ing the
first part of the Module's F<*.pm> file and then requesting some groups of
symbols be exported/imported into a dummy package. Then writes C or C++
code that can convert each C constant into a Perl "constant subroutine"
whose name is the constant's name and whose value is the constant's value.
=cut
sub Myconst2perl
{
my( $pkg, %spec )= @_;
my( $outfile, $writeperl, $ifdef, $export, $importto, @importlist,
@perlfile, %perlcode, @cfile, %ccode, $routine );
ParseAttribs( $pkg, \%spec, {
DO_EXPORT => \$export,
IMPORT_TO => \$importto,
IMPORT_LIST => \@importlist,
IFDEF => \$ifdef,
WRITE_PERL => \$writeperl,
OUTFILE => \$outfile,
PERL_FILE_LIST => \@perlfile,
PERL_FILE_CODES => \%perlcode,
C_FILE_LIST => \@cfile,
C_FILE_CODES => \%ccode,
SUBROUTINE => \$routine,
} );
my $module= ( split /::/, $pkg )[-1];
warn "Writing $outfile...\n";
open( STDOUT, ">$outfile" ) or die "Can't create $outfile: $!\n";
my $code= "";
my $file;
foreach $file ( @perlfile ) {
warn "Reading Perl file, $file: $perlcode{$file}\n";
open( MODULE, "<$file" ) or die "Can't read Perl file, $file: $!\n";
eval qq[
while( <MODULE> ) {
$perlcode{$file};
\$code .= \$_;
}
1;
] or die "$file eval: $@\n";
close( MODULE );
}
print
"/* $outfile - Generated by ExtUtils::Myconst2perl::Myconst2perl */\n";
if( $routine ) {
print "/* See start of $routine() for generation parameters used */\n";
#print "#define main _main_proto"
# " /* Ignore Perl's main() prototype */\n\n";
if( $writeperl ) {
# Here are more reasons why the WRITE_PERL option is discouraged.
if( $Config{useperlio} ) {
print "#define PERLIO_IS_STDIO 1\n";
}
print "#define WIN32IO_IS_STDIO 1\n"; # May cause a warning
print "#define NO_XSLOCKS 1\n"; # What a hack!
}
foreach $file ( @cfile ) {
warn "Reading C file, $file: $ccode{$file}\n";
open( XS, "<$file" ) or die "Can't read C file, $file: $!\n";
my $code= $ccode{$file};
$code =~ s#\\#\\\\#g;
$code =~ s#([^\s -~])#"\\x".sprintf "%02X",unpack "C",$1#ge;
$code =~ s#[*]/#*\\/#g;
print qq[\n/* Include $file: $code */\n];
print qq[\n#line 1 "$file"\n];
eval qq[
while( <XS> ) {
$ccode{$file};
print;
}
1;
] or die "$file eval: $@\n";
close( XS );
}
#print qq[\n#undef main\n];
print qq[\n#define CONST2WRITE_PERL\n];
print qq[\n#include "const2perl.h"\n\n];
if( $writeperl ) {
print "int\nmain( int argc, char *argv[], char *envp[] )\n";
} else {
print "void\n$routine( void )\n";
}
}
print "{\n";
{
@ExtUtils::Myconst2perl::importlist= @importlist;
my $var= '@ExtUtils::Myconst2perl::importlist';
my $port= $export ? "export" : "import";
my $arg2= $export ? "q[$importto]," : "";
local( $^W )= 0;
eval $code . "{\n"
. " { package $importto;\n"
. " warn qq[\u${port}ing to $importto: $var\\n];\n"
. " \$pkg->$port( $arg2 $var );\n"
. " }\n"
. " { no strict 'refs';\n"
. " $var= sort keys %{'_constants::'}; }\n"
. " warn 0 + $var, qq[ symbols ${port}ed.\\n];\n"
. "}\n1;\n"
or die "eval: $@\n";
}
my @syms= @ExtUtils::Myconst2perl::importlist;
my $if;
my $const;
print qq[ START_CONSTS( "$pkg" ) /* No ";" */\n];
{
my( $head, $tail )= ( "/*", "\n" );
if( $writeperl ) {
$head= ' printf( "#';
$tail= '\\n" );' . "\n";
print $head, " Generated by $outfile.", $tail;
}
print $head, " Package $pkg with options:", $tail;
$head= " *" if ! $writeperl;
my $key;
foreach $key ( sort keys %spec ) {
my $val= neatvalue($spec{$key});
$val =~ s/\\/\\\\/g if $writeperl;
print $head, " $key => ", $val, $tail;
}
print $head, " Perl files eval'd:", $tail;
foreach $key ( @perlfile ) {
my $code= $perlcode{$key};
$code =~ s#\\#\\\\#g;
$code =~ s#([^ -~])#"\\".sprintf "%03o",unpack "C",$1#ge;
$code =~ s#"#\\"#g if $writeperl;
print $head, " $key => ", $code, $tail;
}
if( $writeperl ) {
print $head, " C files included:", $tail;
foreach $key ( @cfile ) {
my $code= $ccode{$key};
$code =~ s#\\#\\\\#g;
$code =~ s#([^ -~])#"\\".sprintf "%03o",unpack "C",$1#ge;
$code =~ s#"#\\"#g;
print $head, " $key => ", $code, $tail;
}
} else {
print " */\n";
}
}
if( ! ref($ifdef) && $ifdef =~ /[^\s\w]/ ) {
my $sub= $ifdef;
$sub= 'sub { local($_)= @_; ' . $sub . ' }'
unless $sub =~ /^\s*sub\b/;
$ifdef= eval $sub;
die "$@: $sub\n" if $@;
if( "CODE" ne ref($ifdef) ) {
die "IFDEF didn't create subroutine reference: eval $sub\n";
}
}
foreach $const ( @syms ) {
$if= "CODE" eq ref($ifdef) ? $ifdef->($const) : $ifdef;
if( ! $if ) {
$if= "";
} elsif( "1" eq $if ) {
$if= "#ifdef $const\n";
} elsif( $if !~ /^#/ ) {
$if= "#ifdef $if\n";
} else {
$if= "$if\n";
}
print $if
. qq[ const2perl( $const );\n];
if( $if ) {
print "#else\n"
. qq[ noconst( $const );\n]
. "#endif\n";
}
}
if( $writeperl ) {
print
qq[ printf( "1;\\n" );\n],
qq[ return( 0 );\n];
}
print "}\n";
}
1;