#!./miniperl =head1 NAME xsubpp - compiler to convert Perl XS code into C code =head1 SYNOPSIS B [B<-C++>] [B<-except>] [B<-typemap typemap>] file.xs =head1 DESCRIPTION I will compile XS code into C code by embedding the constructs necessary to let C functions manipulate Perl values and creates the glue necessary to let Perl access those functions. The compiler uses typemaps to determine how to map C function parameters and variables to Perl values. The compiler will search for typemap files called I. It will use the following search path to find default typemaps, with the rightmost typemap taking precedence. ../../../typemap:../../typemap:../typemap:typemap =head1 OPTIONS =over 5 =item B<-C++> Adds ``extern "C"'' to the C code. =item B<-except> Adds exception handling stubs to the C code. =item B<-typemap typemap> Indicates that a user-supplied typemap should take precedence over the default typemaps. This option may be used multiple times, with the last typemap having the highest precedence. =back =head1 ENVIRONMENT No environment variables are used. =head1 AUTHOR Larry Wall =head1 SEE ALSO perl(1) =cut $usage = "Usage: xsubpp [-C++] [-except] [-typemap typemap] file.xs\n"; SWITCH: while ($ARGV[0] =~ s/^-//) { $flag = shift @ARGV; $spat = shift, next SWITCH if $flag eq 's'; $cplusplus = 1, next SWITCH if $flag eq 'C++'; $except = 1, next SWITCH if $flag eq 'except'; push(@tm,shift), next SWITCH if $flag eq 'typemap'; die $usage; } @ARGV == 1 or die $usage; chop($pwd = `pwd`); # Check for error message from VMS if ($pwd =~ /unrecognized command verb/) { $Is_VMS = 1; $pwd = $ENV{DEFAULT} } ($dir, $filename) = @ARGV[0] =~ m#(.*)/(.*)# or ($dir, $filename) = @ARGV[0] =~ m#(.*[>\]])(.*)# or ($dir, $filename) = ('.', $ARGV[0]); chdir($dir); $typemap = shift @ARGV; foreach $typemap (@tm) { die "Can't find $typemap in $pwd\n" unless -r $typemap; } unshift @tm, qw(../../../../lib/ExtUtils/typemap ../../../lib/ExtUtils/typemap ../../lib/ExtUtils/typemap ../../../typemap ../../typemap ../typemap typemap); foreach $typemap (@tm) { open(TYPEMAP, $typemap) || next; $mode = Typemap; $current = \$junk; while () { next if /^#/; if (/^INPUT\s*$/) { $mode = Input, next } if (/^OUTPUT\s*$/) { $mode = Output, next } if (/^TYPEMAP\s*$/) { $mode = Typemap, next } if ($mode eq Typemap) { chop; ($typename, $kind) = split(/\t+/, $_, 2); $type_kind{$typename} = $kind if $kind ne ''; } elsif ($mode eq Input) { if (/^\s/) { $$current .= $_; } else { s/\s*$//; $input_expr{$_} = ''; $current = \$input_expr{$_}; } } else { if (/^\s/) { $$current .= $_; } else { s/\s*$//; $output_expr{$_} = ''; $current = \$output_expr{$_}; } } } close(TYPEMAP); } foreach $key (keys %input_expr) { $input_expr{$key} =~ s/\n+$//; } sub Q { local $text = shift; $text =~ tr/#//d; $text =~ s/\[\[/{/g; $text =~ s/\]\]/}/g; $text; } open(F, $filename) || die "cannot open $filename\n"; while () { last if ($Module, $foo, $Package, $foo1, $Prefix) = /^MODULE\s*=\s*([\w:]+)(\s+PACKAGE\s*=\s*([\w:]+))?(\s+PREFIX\s*=\s*(\S+))?\s*$/; print $_; } exit 0 if $_ eq ""; $lastline = $_; sub fetch_para { # parse paragraph @line = (); if ($lastline ne "") { if ($lastline =~ /^MODULE\s*=\s*([\w:]+)(\s+PACKAGE\s*=\s*([\w:]+))?(\s+PREFIX\s*=\s*(\S+))?\s*$/) { $Module = $1; $foo = $2; $Package = $3; $foo1 = $4; $Prefix = $5; ($Module_cname = $Module) =~ s/\W/_/g; ($Packid = $Package) =~ s/:/_/g; $Packprefix = $Package; $Packprefix .= "::" if defined $Packprefix && $Packprefix ne ""; while () { chop; next if /^#/ && !/^#[ \t]*(if|ifdef|ifndef|else|elif|endif|define|undef)\b/; last if /^\S/; } push(@line, $_) if $_ ne ""; } else { push(@line, $lastline); } $lastline = ""; while () { next if /^#/ && !/^#[ \t]*(if|ifdef|ifndef|else|elif|endif|define|undef)\b/; chop; if (/^\S/ && @line && $line[-1] eq "") { $lastline = $_; last; } else { push(@line, $_); } } pop(@line) while @line && $line[-1] =~ /^\s*$/; } $PPCODE = grep(/PPCODE:/, @line); scalar @line; } while (&fetch_para) { # initialize info arrays undef(%args_match); undef(%var_types); undef(%var_addr); undef(%defaults); undef($class); undef($static); undef($elipsis); # extract return type, function name and arguments $ret_type = shift(@line); if ($ret_type =~ /^BOOT:/) { push (@BootCode, @line, "", "") ; next ; } if ($ret_type =~ /^static\s+(.*)$/) { $static = 1; $ret_type = $1; } $func_header = shift(@line); ($func_name, $orig_args) = $func_header =~ /^([\w:]+)\s*\((.*)\)$/; if ($func_name =~ /(.*)::(.*)/) { $class = $1; $func_name = $2; } ($pname = $func_name) =~ s/^($Prefix)?/$Packprefix/; push(@Func_name, "${Packid}_$func_name"); push(@Func_pname, $pname); @args = split(/\s*,\s*/, $orig_args); if (defined($class)) { if (defined($static)) { unshift(@args, "CLASS"); $orig_args = "CLASS, $orig_args"; $orig_args =~ s/^CLASS, $/CLASS/; } else { unshift(@args, "THIS"); $orig_args = "THIS, $orig_args"; $orig_args =~ s/^THIS, $/THIS/; } } $orig_args =~ s/"/\\"/g; $min_args = $num_args = @args; foreach $i (0..$num_args-1) { if ($args[$i] =~ s/\.\.\.//) { $elipsis = 1; $min_args--; if ($args[i] eq '' && $i == $num_args - 1) { pop(@args); last; } } if ($args[$i] =~ /([^=]*\S)\s*=\s*(.*)/) { $min_args--; $args[$i] = $1; $defaults{$args[$i]} = $2; $defaults{$args[$i]} =~ s/"/\\"/g; } } if (defined($class)) { $func_args = join(", ", @args[1..$#args]); } else { $func_args = join(", ", @args); } @args_match{@args} = 1..@args; # print function header print Q<<"EOF"; #XS(XS_${Packid}_$func_name) #[[ # dXSARGS; EOF if ($elipsis) { $cond = qq(items < $min_args); } elsif ($min_args == $num_args) { $cond = qq(items != $min_args); } else { $cond = qq(items < $min_args || items > $num_args); } print Q<<"EOF" if $except; # char errbuf[1024]; # *errbuf = '\0'; EOF print Q<<"EOF"; # if ($cond) { # croak("Usage: $pname($orig_args)"); # } EOF print Q<<"EOF" if $PPCODE; # SP -= items; EOF # Now do a block of some sort. $condnum = 0; if (!@line) { @line = "CLEANUP:"; } while (@line) { if ($_[0] =~ s/^\s*CASE\s*:\s*//) { $cond = shift(@line); if ($condnum == 0) { print " if ($cond)\n"; } elsif ($cond ne '') { print " else if ($cond)\n"; } else { print " else\n"; } $condnum++; } if ($except) { print Q<<"EOF"; # TRY [[ EOF } else { print Q<<"EOF"; # [[ EOF } # do initialization of input variables $thisdone = 0; $retvaldone = 0; $deferred = ""; while (@line) { $_ = shift(@line); last if /^\s*NOT_IMPLEMENTED_YET/; last if /^\s*(PPCODE|CODE|OUTPUT|CLEANUP|CASE)\s*:/; ($var_type, $var_name, $var_init) = /\s*([^\t]+)\s*([^\s=]+)\s*(=.*)?/; # Catch common errors. More error checking required here. blurt("Error: no tab in $pname argument declaration '$_'\n") unless (m/\S+\s*\t\s*\S+/); # catch C style argument declaration (this could be made alowable syntax) warn("Warning: ignored semicolon in $pname argument declaration '$_'\n") if ($var_name =~ s/;//g); # eg SV *name; # catch many errors similar to: SV* name blurt("Error: invalid $pname argument name '$var_name' (type '$var_type')\n") unless ($var_name =~ m/^&?\w+$/); if ($var_name =~ /^&/) { $var_name =~ s/^&//; $var_addr{$var_name} = 1; } $thisdone |= $var_name eq "THIS"; $retvaldone |= $var_name eq "RETVAL"; $var_types{$var_name} = $var_type; print "\t" . &map_type($var_type); $var_num = $args_match{$var_name}; if ($var_addr{$var_name}) { $func_args =~ s/\b($var_name)\b/&\1/; } if ($var_init !~ /^=\s*NO_INIT\s*$/) { if ($var_init !~ /^\s*$/) { &output_init($var_type, $var_num, "$var_name $var_init"); } elsif ($var_num) { # generate initialization code &generate_init($var_type, $var_num, $var_name); } else { print ";\n"; } } else { print "\t$var_name;\n"; } } if (!$thisdone && defined($class)) { if (defined($static)) { print "\tchar *"; $var_types{"CLASS"} = "char *"; &generate_init("char *", 1, "CLASS"); } else { print "\t$class *"; $var_types{"THIS"} = "$class *"; &generate_init("$class *", 1, "THIS"); } } # do code if (/^\s*NOT_IMPLEMENTED_YET/) { print "\ncroak(\"$pname: not implemented yet\");\n"; } else { if ($ret_type ne "void") { print "\t" . &map_type($ret_type) . "\tRETVAL;\n" if !$retvaldone; $args_match{"RETVAL"} = 0; $var_types{"RETVAL"} = $ret_type; } if (/^\s*PPCODE:/) { print $deferred; while (@line) { $_ = shift(@line); die "PPCODE must be last thing" if /^\s*(OUTPUT|CLEANUP|CASE)\s*:/; print "$_\n"; } print "\tPUTBACK;\n\treturn;\n"; } elsif (/^\s*CODE:/) { print $deferred; while (@line) { $_ = shift(@line); last if /^\s*(OUTPUT|CLEANUP|CASE)\s*:/; print "$_\n"; } } elsif ($func_name eq "DESTROY") { print $deferred; print "\n\t"; print "delete THIS;\n" } else { print $deferred; print "\n\t"; if ($ret_type ne "void") { print "RETVAL = "; } if (defined($static)) { if ($func_name =~ /^new/) { $func_name = "$class"; } else { print "$class::"; } } elsif (defined($class)) { print "THIS->"; } if (defined($spat) && $func_name =~ /^($spat)(.*)$/) { $func_name = $2; } print "$func_name($func_args);\n"; &generate_output($ret_type, 0, "RETVAL") unless $ret_type eq "void"; } } # do output variables if (/^\s*OUTPUT\s*:/) { while (@line) { $_ = shift(@line); last if /^\s*CLEANUP\s*:/; s/^\s+//; ($outarg, $outcode) = split(/\t+/); if ($outcode) { print "\t$outcode\n"; } else { die "$outarg not an argument" unless defined($args_match{$outarg}); $var_num = $args_match{$outarg}; &generate_output($var_types{$outarg}, $var_num, $outarg); } } } # do cleanup if (/^\s*CLEANUP\s*:/) { while (@line) { $_ = shift(@line); last if /^\s*CASE\s*:/; print "$_\n"; } } # print function trailer if ($except) { print Q<