package ExtUtils::ParseXS;
use Cwd;
use Config;
use File::Basename;
use File::Spec;
use Symbol;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(process_file);
my(@XSStack); # Stack of conditionals and INCLUDEs
my($XSS_work_idx, $cpp_next_tmp);
our ($VERSION);
$VERSION = '2.19_01';
our (%input_expr, %output_expr, $ProtoUsed, @InitFileCode, $FH, $proto_re, $Overload, $errors, $Fallback
, $cplusplus, $hiertype, $WantPrototypes, $WantVersionChk, $except, $WantLineNumbers
, $WantOptimize, $process_inout, $process_argtypes, @tm
, $dir, $filename, $filepathname, %IncludedFiles
, %type_kind, %proto_letter
, %targetable, $BLOCK_re, $lastline, $lastline_no
, $Package, $Prefix, @line, @BootCode, %args_match, %defaults, %var_types, %arg_list, @proto_arg
, $processing_arg_with_types, %argtype_seen, @outlist, %in_out, %lengthof
, $proto_in_this_xsub, $scope_in_this_xsub, $interface, $prepush_done, $interface_macro, $interface_macro_set
, $ProtoThisXSUB, $ScopeThisXSUB, $xsreturn
, @line_no, $ret_type, $func_header, $orig_args
, ); # Add these just to get compilation to happen.
our ($func_args, $PPCODE, $CODE, $EXPLICIT_RETURN, $ALIAS, $INTERFACE, $Full_func_name,
$cond, $min_args, $pname, $condnum, $thisdone, $retvaldone, $deferred, $gotRETVAL,
$RETVAL_code, %outargs, $DoSetMagic, %XsubAliases, $name, $value, @Attributes,
$XsubAliases, %Interfaces, $Packid, $proto, $Module_cname, $Interfaces,
$name_printed, $var_num, $orig_alias, $Packprefix, %XsubAliasValues, $Module,
$type, $num, $var, $init, $arg, $argoff, $ntype, $tk, $subtype,
$func_name, $expr, $do_setmagic, $do_push, $subexpr);
my $output_fh;
# Constants:
our $END = "!End!\n\n"; # "impossible" keyword (multiple newline)
our $Is_VMS = $^OS_NAME eq 'VMS';
# constant regex.
our ($C_group_rex, $C_arg);
# Group in C (no support for comments or literals)
$C_group_rex = qr/ [({\[]
(?: (?> [^()\[\]{}]+ ) | (??{ $C_group_rex }) )*
[)}\]] /x ;
# Chunk in C without comma at toplevel (no comments):
$C_arg = qr/ (?: (?> [^()\[\]{},"']+ )
| (??{ $C_group_rex })
| " (?: (?> [^\\"]+ )
| \\.
)* " # String literal
| ' (?: (?> [^\\']+ ) | \\. )* ' # Char literal
)* /xs;
our $SymSet;
sub process_file {
# Allow for $package->process_file(%hash) in the future
my @($pkg, %< %args) = (nelems @_) % 2 ?? @_ !! @(__PACKAGE__, < @_);
$ProtoUsed = exists %args{prototypes};
# Set defaults.
%args = %(
# 'C++' => 0, # Doesn't seem to *do* anything...
hiertype => 0,
except => 0,
prototypes => 0,
versioncheck => 1,
linenumbers => 1,
optimize => 1,
prototypes => 0,
inout => 1,
argtypes => 1,
typemap => \@(),
output => $^STDOUT,
csuffix => '.c',
< %args,
);
# Global Constants
if ($Is_VMS) {
# Establish set of global symbols with max length 28, since xsubpp
# will later add the 'XS_' prefix.
require ExtUtils::XSSymSet;
$SymSet = ExtUtils::XSSymSet->new( 28);
}
@XSStack = @(\%(type => 'none'));
@($XSS_work_idx, $cpp_next_tmp) = @(0, "XSubPPtmpAAAA");
@InitFileCode = @( () );
$FH = Symbol::gensym();
$proto_re = "[" . quotemeta('\$%&*@;[]') . "]" ;
$Overload = 0;
$errors = 0;
$Fallback = '&PL_sv_undef';
# Most of the 1500 lines below uses these globals. We'll have to
# clean this up sometime, probably. For now, we just pull them out
# of %args. -Ken
$cplusplus = %args{?'C++'};
$hiertype = %args{?hiertype};
$WantPrototypes = %args{?prototypes};
$WantVersionChk = %args{?versioncheck};
$except = %args{?except} ?? ' TRY' !! '';
$WantLineNumbers = %args{?linenumbers};
$WantOptimize = %args{?optimize};
$process_inout = %args{?inout};
$process_argtypes = %args{?argtypes};
@tm = @( ref %args{?typemap} ?? < @{%args{?typemap}} !! (%args{?typemap}) );
for (@(%args{?filename})) {
die "Missing required parameter 'filename'" unless $_;
$filepathname = $_;
@($dir, $filename) = @( dirname($_), basename($_));
$filepathname =~ s/\\/\\\\/g;
%IncludedFiles{+$_}++;
}
# Open the input file
open($FH, "<", %args{?filename}) or die "cannot open %args{?filename}: $^OS_ERROR\n";
# Open the output file if given as a string. If they provide some
# other kind of reference, trust them that we can print to it.
if (not ref %args{?output}) {
open my($fh), ">", "%args{?output}" or die "Can't create %args{?output}: $^OS_ERROR";
%args{+outfile} = %args{?output};
%args{+output} = $fh;
}
$output_fh = %args{output};
# Really, we shouldn't have to chdir() in the first
# place. For now, just save & restore.
my $orig_cwd = cwd();
chdir($dir);
my $pwd = cwd();
my $csuffix = %args{?csuffix};
if ($WantLineNumbers) {
my $cfile;
if ( %args{?outfile} ) {
$cfile = %args{?outfile};
} else {
$cfile = %args{?filename};
$cfile =~ s/\.xs$/$csuffix/i or $cfile .= $csuffix;
}
$ExtUtils::ParseXS::CountLines::cfile = $cfile;
binmode $output_fh, ':via(ExtUtils::ParseXS::CountLines)';
}
foreach my $typemap ( @tm) {
die "Can't find $typemap in $pwd\n" unless -r $typemap;
}
push @tm, < standard_typemap_locations();
foreach my $typemap ( @tm) {
next unless -f $typemap ;
# skip directories, binary files etc.
warn("Warning: ignoring non-text typemap file '$typemap'\n"), next
unless -T $typemap ;
open(my $typemap_fh, "<", $typemap)
or warn ("Warning: could not open typemap file '$typemap': $^OS_ERROR\n"), next;
my $mode = 'Typemap';
my $junk = "" ;
my $current = \$junk;
while ( ~< $typemap_fh) {
next if m/^\s* #/;
my $line_no = iohandle::input_line_number($typemap_fh) + 1;
if (m/^INPUT\s*$/) {
$mode = 'Input'; $current = \$junk; next;
}
if (m/^OUTPUT\s*$/) {
$mode = 'Output'; $current = \$junk; next;
}
if (m/^TYPEMAP\s*$/) {
$mode = 'Typemap'; $current = \$junk; next;
}
if ($mode eq 'Typemap') {
chomp;
my $line = $_ ;
TrimWhitespace($_) ;
# skip blank lines and comment lines
next if m/^$/ or m/^#/ ;
my @($type,$kind, $proto) = @: m/^\s*(.*?\S)\s+(\S+)\s*($proto_re*)\s*$/ or
warn("Warning: File '$typemap' Line $($line_no-1) '$line' TYPEMAP entry needs 2 or 3 columns\n"), next;
$type = TidyType($type) ;
%type_kind{+$type} = $kind ;
# prototype defaults to '$'
$proto = "\$" unless $proto ;
warn("Warning: File '$typemap' Line $($line_no-1) '$line' Invalid prototype '$proto'\n")
unless ValidProtoString($proto) ;
%proto_letter{+$type} = C_string($proto) ;
} elsif (m/^\s/) {
$$current .= $_;
} elsif ($mode eq 'Input') {
s/\s+$//;
%input_expr{+$_} = '';
$current = \%input_expr{+$_};
} else {
s/\s+$//;
%output_expr{+$_} = '';
$current = \%output_expr{+$_};
}
}
close($typemap_fh);
}
foreach my $value (values %input_expr) {
$value =~ s/;*\s+\z//;
# Move C pre-processor instructions to column 1 to be strictly ANSI
# conformant. Some pre-processors are fussy about this.
$value =~ s/^\s+#/#/mg;
}
foreach my $value (values %output_expr) {
# And again.
$value =~ s/^\s+#/#/mg;
}
my ($cast, $size);
our $bal;
$bal = qr[(?:(?>[^()]+)|\((??{ $bal })\))*]; # ()-balanced
$cast = qr[(?:\(\s*SV\s*\*\s*\)\s*)?]; # Optional (SV*) cast
$size = qr[,\s* (??{ $bal }) ]x; # Third arg (to setpvn)
foreach my $key (keys %output_expr) {
BEGIN { $^HINT_BITS ^|^= 0x00200000 }; # Equivalent to: use re 'eval', but hardcoded so we can compile re.xs
my @(?$t, ?$with_size, ?$arg, ?$sarg) =
@(%output_expr{?$key} =~
m[^ \s+ sv_set ( [iunp] ) v (n)? # Type, is_setpvn
\s* \( \s* $cast \$arg \s* ,
\s* ( (??{ $bal }) ) # Set from
( (??{ $size }) )? # Possible sizeof set-from
\) \s* ; \s* $
]x);
%targetable{+$key} = \@($t, $with_size, $arg, $sarg) if $t;
}
# Match an XS keyword
$BLOCK_re= '\s*(' . join('|', qw(
REQUIRE BOOT CASE PREINIT INPUT INIT CODE PPCODE OUTPUT
CLEANUP ALIAS ATTRS PROTOTYPES PROTOTYPE VERSIONCHECK INCLUDE
SCOPE INTERFACE INTERFACE_MACRO C_ARGS POSTCALL FALLBACK
)) . "|$END)\\s*:";
# Identify the version of xsubpp used
print $output_fh, <<EOM ;
/*
* This file was generated automatically by ExtUtils::ParseXS version $VERSION from the
* contents of $filename. Do not edit this file, edit $filename instead.
*
* ANY CHANGES MADE HERE WILL BE LOST!
*
*/
EOM
print($output_fh, "#line 1 \"$filepathname\"\n")
if $WantLineNumbers;
my $line;
firstmodule:
while ($line = ~< $FH) {
$_ = $line;
if (m/^=/) {
my $podstartline = iohandle::input_line_number($FH);
{
$_ = $line;
if (m/^=cut\s*$/) {
# We can't just write out a /* */ comment, as our embedded
# POD might itself be in a comment. We can't put a /**/
# comment inside #if 0, as the C standard says that the source
# file is decomposed into preprocessing characters in the stage
# before preprocessing commands are executed.
# I don't want to leave the text as barewords, because the spec
# isn't clear whether macros are expanded before or after
# preprocessing commands are executed, and someone pathological
# may just have defined one of the 3 words as a macro that does
# something strange. Multiline strings are illegal in C, so
# the "" we write must be a string literal. And they aren't
# concatenated until 2 steps later, so we are safe.
# - Nicholas Clark
print($output_fh, "#if 0\n \"Skipped embedded POD.\"\n#endif\n");
print($output_fh, "#line " . (iohandle::input_line_number($FH) + 1)
. " \"$filepathname\"\n")
if $WantLineNumbers;
next firstmodule
}
} while ($line = ~< $FH);
# At this point $. is at end of file so die won't state the start
# of the problem, and as we haven't yet read any lines &death won't
# show the correct line in the message either.
die ("Error: Unterminated pod in $filename, line $podstartline\n")
unless $lastline;
}
last if @(?$Package, ?$Prefix) =
@: m/^MODULE\s*=\s*[\w:]+(?:\s+PACKAGE\s*=\s*([\w:]+))?(?:\s+PREFIX\s*=\s*(\S+))?\s*$/;
print $output_fh, $_;
}
unless (defined $line) {
warn "Didn't find a 'MODULE ... PACKAGE ... PREFIX' line\n";
exit 0; # Not a fatal error for the caller process
}
print $output_fh, <<"EOF";
#ifndef PERL_UNUSED_VAR
# define PERL_UNUSED_VAR(var) if (0) var = var
#endif
EOF
print $output_fh, 'ExtUtils::ParseXS::CountLines'->end_marker . "\n" if $WantLineNumbers;
$lastline = $line;
$lastline_no = iohandle::input_line_number($FH);
PARAGRAPH:
while (fetch_para()) {
process_para(< %args);
}
if ($Overload) # make it findable with fetchmethod
{
print $output_fh, < Q(<<"EOF");
#XS(XS_$($Packid)_nil); /* prototype to pass -Wmissing-prototypes */
#XS(XS_$($Packid)_nil)
#\{
# XSRETURN_EMPTY;
#\}
#
EOF
unshift(@InitFileCode, <<"MAKE_FETCHMETHOD_WORK");
/* Making a sub named "$($Package)::()" allows the package */
/* to be findable via fetchmethod(), and causes */
/* overload::Overloaded("$Package") to return true. */
newXS("$($Package)::()", XS_$($Packid)_nil, file$proto);
MAKE_FETCHMETHOD_WORK
}
# print initialization routine
print $output_fh, Q(<<"EOF");
##ifdef __cplusplus
#extern "C"
##endif
EOF
print $output_fh, Q(<<"EOF");
#XS(boot_$Module_cname); /* prototype to pass -Wmissing-prototypes */
#XS(boot_$Module_cname)
EOF
print $output_fh, Q(<<"EOF");
#[[
##ifdef dVAR
# dVAR; dXSARGS;
##else
# dXSARGS;
##endif
EOF
#-Wall: if there is no $Full_func_name there are no xsubs in this .xs
#so `file' is unused
print $output_fh, Q(<<"EOF") if $Full_func_name;
# char* file = __FILE__;
EOF
print $output_fh, Q("#\n");
print $output_fh, Q(<<"EOF");
# PERL_UNUSED_VAR(cv); /* -W */
# PERL_UNUSED_VAR(items); /* -W */
EOF
print $output_fh, Q(<<"EOF") if $WantVersionChk ;
# XS_VERSION_BOOTCHECK ;
#
EOF
print $output_fh, Q(<<"EOF") if defined $XsubAliases or defined $Interfaces ;
# \{
# CV * cv ;
#
EOF
print $output_fh, Q(<<"EOF") if ($Overload);
# /* register the overloading (type 'A') magic */
# PL_amagic_generation++;
# /* The magic for overload gets a GV* via gv_fetchmeth as */
# /* mentioned above, and looks in the SV* slot of it for */
# /* the "fallback" status. */
# sv_setsv(
# get_sv( "$Package::()", TRUE ),
# $Fallback
# );
EOF
print $output_fh, < @InitFileCode;
print $output_fh, Q(<<"EOF") if defined $XsubAliases or defined $Interfaces ;
# \}
EOF
if ((nelems @BootCode))
{
print $output_fh, "\n /* Initialisation Section */\n\n" ;
@line = @BootCode;
print_section( output => $output_fh );
print $output_fh, "\n /* End of Initialisation Section */\n\n" ;
}
print $output_fh, <<'EOF';
if (PL_unitcheckav)
call_list(PL_scopestack_ix, PL_unitcheckav);
EOF
print $output_fh, Q(<<"EOF");
# XSRETURN_YES;
#]]
#
EOF
warn("Please specify prototyping behavior for $filename (see perlxs manual)\n")
unless $ProtoUsed ;
chdir($orig_cwd);
$output_fh = undef;
close $FH;
return 1;
}
sub process_para {
my %args = %( < @_ );
# Print initial preprocessor statements and blank lines
while ((nelems @line) && @line[0] !~ m/^[^\#]/) {
my $line = shift(@line);
print $output_fh, $line . "\n";
next unless $line =~ m/^\#\s*(if(?:n?def)?|elsif|else|endif)\b/;
my $statement = $1;
if ($statement =~ m/^if/) {
$XSS_work_idx = (nelems @XSStack);
push(@XSStack, \%(type => 'if'));
} else {
death ("Error: `$statement' with no matching `if'")
if @XSStack[-1]->{?type} ne 'if';
if (@XSStack[-1]->{?varname}) {
push(@InitFileCode, "#endif\n");
push(@BootCode, "#endif");
}
my @fns = keys %{@XSStack[-1]->{?functions} || \%()};
if ($statement ne 'endif') {
# Hide the functions defined in other #if branches, and reset.
%{@XSStack[-1]->{+other_functions}}{[ @fns]} = @(1) x nelems @fns;
%{@XSStack[-1]}{[qw(varname functions)]} = @('', \%());
} else {
my $tmp = pop(@XSStack);
0 while (--$XSS_work_idx
&& @XSStack[$XSS_work_idx]->{?type} ne 'if');
# Keep all new defined functions
push(@fns, < keys %{$tmp->{?other_functions} || \%()});
%{@XSStack[$XSS_work_idx]->{+functions}}{[ @fns]} = @(1) x nelems @fns;
}
}
}
return unless (nelems @line);
if ($XSS_work_idx && !@XSStack[$XSS_work_idx]->{?varname}) {
# We are inside an #if, but have not yet #defined its xsubpp variable.
print $output_fh, "#define $cpp_next_tmp 1\n\n";
push(@InitFileCode, "#if $cpp_next_tmp\n");
push(@BootCode, "#if $cpp_next_tmp");
@XSStack[$XSS_work_idx]->{+varname} = $cpp_next_tmp++;
}
death ("Code is not inside a function"
." (maybe last function was ended by a blank line "
." followed by a statement on column one?)")
if @line[0] =~ m/^\s/;
my ($class, $externC, $static, $ellipsis, $wantRETVAL, $RETVAL_no_return);
my (@fake_INPUT_pre); # For length(s) generated variables
my (@fake_INPUT);
# initialize info arrays
undef(%args_match);
undef(%var_types);
undef(%defaults);
undef(%arg_list) ;
undef(@proto_arg) ;
undef($processing_arg_with_types) ;
undef(%argtype_seen) ;
undef(@outlist) ;
undef(%in_out) ;
undef(%lengthof) ;
undef($proto_in_this_xsub) ;
undef($scope_in_this_xsub) ;
undef($interface);
undef($prepush_done);
$interface_macro = 'XSINTERFACE_FUNC' ;
$interface_macro_set = 'XSINTERFACE_FUNC_SET' ;
$ProtoThisXSUB = $WantPrototypes ;
$ScopeThisXSUB = 0;
$xsreturn = 0;
$_ = shift(@line);
while (my $kwd = check_keyword("REQUIRE|PROTOTYPES|FALLBACK|VERSIONCHECK|INCLUDE")) {
&{*{Symbol::fetch_glob("$($kwd)_handler")}}() ;
next PARAGRAPH unless (nelems @line) ;
$_ = shift(@line);
}
my $begin_lineno = @line_no[(nelems @line_no) - nelems @line];
if (check_keyword("BOOT")) {
&check_cpp( < @_ );
push (@BootCode, "#line $begin_lineno \"$filepathname\"\n")
if $WantLineNumbers && @line[0] !~ m/^\s*#\s*line\b/;
push (@BootCode, < @line, "") ;
next PARAGRAPH ;
}
# extract return type, function name and arguments
($ret_type) = TidyType($_);
$RETVAL_no_return = 1 if $ret_type =~ s/^NO_OUTPUT\s+//;
# Allow one-line ANSI-like declaration
unshift @line, $2
if $process_argtypes
and $ret_type =~ s/^(.*?\w.*?)\s*\b(\w+\s*\(.*)/$1/s;
# a function definition needs at least 2 lines
blurt ("Error: Function definition too short '$ret_type'"), next PARAGRAPH
unless (nelems @line) ;
$externC = 1 if $ret_type =~ s/^extern "C"\s+//;
$static = 1 if $ret_type =~ s/^static\s+//;
$func_header = shift(@line);
blurt ("Error: Cannot parse function definition from '$func_header'"), next PARAGRAPH
unless $func_header =~ m/^(?:([\w:]*)::)?(\w+)\s*\(\s*(.*?)\s*\)\s*(const)?\s*(;\s*)?$/s;
@($class, $func_name, $orig_args) = @($1, $2, $3) ;
$class = "$4 $class" if $4;
($pname = $func_name) =~ s/^($Prefix)?/$Packprefix/;
(my $clean_func_name = $func_name) =~ s/^$Prefix//;
$Full_func_name = "$($Packid)_$clean_func_name";
if ($Is_VMS) {
$Full_func_name = $SymSet->addsym($Full_func_name);
}
# Check for duplicate function definition
for my $tmp ( @XSStack) {
next unless defined $tmp->{?functions}->{?$Full_func_name};
Warn("Warning: duplicate function definition '$clean_func_name' detected");
last;
}
@XSStack[$XSS_work_idx]->{?functions}->{+$Full_func_name} ++ ;
@Attributes = @();
%XsubAliases = %XsubAliasValues = %Interfaces = %();
$DoSetMagic = 1;
$orig_args =~ s/\\\s*/ /g; # process line continuations
my @args;
my %only_C_inlist; # Not in the signature of Perl function
if ($process_argtypes and $orig_args =~ m/\S/) {
my $args = "$orig_args ,";
if ($args =~ m/^( (??{ $C_arg }) , )* $ /x) {
@args = @($args =~ m/\G ( (??{ $C_arg }) ) , /xg);
for ( @args ) {
s/^\s+//;
s/\s+$//;
my @($arg, $default) = @: m/ ( [^=]* ) ( (?: = .* )? ) /x;
my @(?$pre, ?$name) = @($arg =~ m/(.*?) \s*
\b ( \w+ | length\( \s*\w+\s* \) )
\s* $ /x);
next unless defined($pre) && length($pre);
my $out_type = '';
my $inout_var;
if ($process_inout and s/^(IN|IN_OUTLIST|OUTLIST|OUT|IN_OUT)\s+//) {
my $type = $1;
$out_type = $type if $type ne 'IN';
$arg =~ s/^(IN|IN_OUTLIST|OUTLIST|OUT|IN_OUT)\s+//;
$pre =~ s/^(IN|IN_OUTLIST|OUTLIST|OUT|IN_OUT)\s+//;
}
my $islength;
if ($name =~ m/^length\( \s* (\w+) \s* \)\z/x) {
$name = "XSauto_length_of_$1";
$islength = 1;
die "Default value on length() argument: `$_'"
if length $default;
}
if (length $pre or $islength) { # Has a type
if ($islength) {
push @fake_INPUT_pre, $arg;
} else {
push @fake_INPUT, $arg;
}
# warn "pushing '$arg'\n";
%argtype_seen{+$name}++;
$_ = "$name$default"; # Assigns to @args
}
%only_C_inlist{+$_} = 1 if $out_type eq "OUTLIST" or $islength;
push @outlist, $name if $out_type =~ m/OUTLIST$/;
%in_out{+$name} = $out_type if $out_type;
}
} else {
@args = split(m/\s*,\s*/, $orig_args);
Warn("Warning: cannot parse argument list '$orig_args', fallback to split");
}
} else {
@args = split(m/\s*,\s*/, $orig_args);
for ( @args) {
if ($process_inout and s/^(IN|IN_OUTLIST|OUTLIST|IN_OUT|OUT)\s+//) {
my $out_type = $1;
next if $out_type eq 'IN';
%only_C_inlist{+$_} = 1 if $out_type eq "OUTLIST";
my $name = ''; # FIXME $name is wronly scoped.
push @outlist, $name if $out_type =~ m/OUTLIST$/;
%in_out{+$_} = $out_type;
}
}
}
my $extra_args = 0;
my @args_num = @( () );
my $num_args = 0;
my $report_args = '';
if (defined($class)) {
my $arg0 = ((defined($static) or $func_name eq 'new')
?? "CLASS" !! "THIS");
unshift(@args, $arg0);
($report_args = "$arg0, $report_args") =~ s/^\w+, $/$arg0/;
}
foreach my $i (0 ..( (nelems @args)-1)) {
if (@args[$i] =~ s/\.\.\.//) {
$ellipsis = 1;
if (@args[$i] eq '' && $i ==( (nelems @args)-1)) {
$report_args .= ", ...";
pop(@args);
last;
}
}
if (%only_C_inlist{?@args[$i]}) {
push @args_num, undef;
} else {
push @args_num, ++$num_args;
$report_args .= ", @args[$i]";
}
if (@args[$i] =~ m/^([^=]*[^\s=])\s*=\s*(.*)/s) {
$extra_args++;
@args[$i] = $1;
%defaults{+@args[$i]} = $2;
%defaults{+@args[$i]} =~ s/"/\\"/g;
}
@proto_arg[+$i+1] = '$' ;
}
my $min_args = $num_args - $extra_args;
$report_args =~ s/"/\\"/g;
$report_args =~ s/^,\s+//;
my @func_args = @args;
shift @func_args if defined($class);
for ( @func_args) {
s/^/&/ if %in_out{?$_};
}
$func_args = join(", ", @func_args);
%args_match{[ @args]} = @args_num;
$PPCODE = grep( {m/^\s*PPCODE\s*:/ }, @line);
$CODE = grep( {m/^\s*CODE\s*:/ }, @line);
# Detect CODE: blocks which use ST(n)= or XST_m*(n,v)
# to set explicit return values.
$EXPLICIT_RETURN = ($CODE &&
("$(join ' ', @line)" =~ m/(\bST\s*\([^;]*=) | (\bXST_m\w+\s*\()/x ));
$ALIAS = grep( {m/^\s*ALIAS\s*:/ }, @line);
$INTERFACE = grep( {m/^\s*INTERFACE\s*:/ }, @line);
$xsreturn = 1 if $EXPLICIT_RETURN;
$externC = $externC ?? qq[extern "C"] !! "";
# print function header
print $output_fh, Q(<<"EOF");
#$externC
#XS(XS_$Full_func_name); /* prototype to pass -Wmissing-prototypes */
#XS(XS_$Full_func_name)
#[[
##ifdef dVAR
# dVAR; dXSARGS;
##else
# dXSARGS;
##endif
EOF
print $output_fh, Q(<<"EOF") if $ALIAS ;
# dXSI32;
EOF
print $output_fh, Q(<<"EOF") if $INTERFACE ;
# dXSFUNCTION($ret_type);
EOF
if ($ellipsis) {
$cond = ($min_args ?? qq(items < $min_args) !! 0);
} elsif ($min_args == $num_args) {
$cond = qq(items != $min_args);
} else {
$cond = qq(items < $min_args || items > $num_args);
}
print $output_fh, Q(<<"EOF") if $except;
# char errbuf[1024];
# *errbuf = '\0';
EOF
if ($ALIAS)
{ print $output_fh, Q(<<"EOF") if $cond }
# if ($cond)
# Perl_croak(aTHX_ "Usage: \%s(\%s)", SvPVX_const(SvNAME(cvTsv(cv))), "$report_args");
EOF
else
{ print $output_fh, Q(<<"EOF") if $cond }
# if ($cond)
# Perl_croak(aTHX_ "Usage: \%s(\%s)", "$pname", "$report_args");
EOF
# cv doesn't seem to be used, in most cases unless we go in
# the if of this else
print $output_fh, Q(<<"EOF");
# PERL_UNUSED_VAR(cv); /* -W */
EOF
#gcc -Wall: if an xsub has PPCODE is used
#it is possible none of ST, XSRETURN or XSprePUSH macros are used
#hence `ax' (setup by dXSARGS) is unused
#XXX: could breakup the dXSARGS; into dSP;dMARK;dITEMS
#but such a move could break third-party extensions
print $output_fh, Q(<<"EOF") if $PPCODE;
# PERL_UNUSED_VAR(ax); /* -Wall */
EOF
print $output_fh, Q(<<"EOF") if $PPCODE;
# SP -= items;
EOF
# Now do a block of some sort.
$condnum = 0;
$cond = ''; # last CASE: condidional
push(@line, "$END:");
push(@line_no, @line_no[-1]);
$_ = '';
&check_cpp( < @_ );
while ((nelems @line)) {
&CASE_handler( < @_ ) if check_keyword("CASE");
print $output_fh, Q(<<"EOF");
# $except [[
EOF
# do initialization of input variables
$thisdone = 0;
$retvaldone = 0;
$deferred = "";
%arg_list = %( () ) ;
$gotRETVAL = 0;
INPUT_handler() ;
process_keyword("INPUT|PREINIT|INTERFACE_MACRO|C_ARGS|ALIAS|ATTRS|PROTOTYPE|SCOPE") ;
print $output_fh, Q(<<"EOF") if $ScopeThisXSUB;
# ENTER;
# [[
EOF
if (!$thisdone && defined($class)) {
if (defined($static) or $func_name eq 'new') {
print $output_fh, "\tchar *";
%var_types{+"CLASS"} = "char *";
&generate_init("char *", 1, "CLASS");
}
else {
print $output_fh, "\t$class *";
%var_types{+"THIS"} = "$class *";
&generate_init("$class *", 1, "THIS");
}
}
# do code
if (m/^\s*NOT_IMPLEMENTED_YET/) {
print $output_fh, "\n\tPerl_croak(aTHX_ \"$pname: not implemented yet\");\n";
$_ = '' ;
} else {
if ($ret_type ne "void") {
print $output_fh, "\t" . &map_type($ret_type, 'RETVAL') . ";\n"
if !$retvaldone;
%args_match{+"RETVAL"} = 0;
%var_types{+"RETVAL"} = $ret_type;
print $output_fh, "\tdXSTARG;\n"
if $WantOptimize and %targetable{?%type_kind{?$ret_type}};
}
if ((nelems @fake_INPUT) or nelems @fake_INPUT_pre) {
unshift @line, < @fake_INPUT_pre, < @fake_INPUT, $_;
$_ = "";
$processing_arg_with_types = 1;
INPUT_handler() ;
}
print $output_fh, $deferred;
process_keyword("INIT|ALIAS|ATTRS|PROTOTYPE|INTERFACE_MACRO|INTERFACE|C_ARGS") ;
if (check_keyword("PPCODE")) {
print_section( output => $output_fh );
death ("PPCODE must be last thing") if (nelems @line);
print $output_fh, "\tLEAVE;\n" if $ScopeThisXSUB;
print $output_fh, "\tPUTBACK;\n\treturn;\n";
} elsif (check_keyword("CODE")) {
print_section( output => $output_fh ) ;
} elsif (defined($class) and $func_name eq "DESTROY") {
print $output_fh, "\n\t";
print $output_fh, "delete THIS;\n";
} else {
print $output_fh, "\n\t";
if ($ret_type ne "void") {
print $output_fh, "RETVAL = ";
$wantRETVAL = 1;
}
if (defined($static)) {
if ($func_name eq 'new') {
$func_name = "$class";
} else {
print $output_fh, "$($class)::";
}
} elsif (defined($class)) {
if ($func_name eq 'new') {
$func_name .= " $class";
} else {
print $output_fh, "THIS->";
}
}
$func_name =~ s/^\Q%args{?'s'}//
if exists %args{'s'};
$func_name = 'XSFUNCTION' if $interface;
print $output_fh, "$func_name($func_args);\n";
}
}
# do output variables
$gotRETVAL = 0; # 1 if RETVAL seen in OUTPUT section;
undef $RETVAL_code ; # code to set RETVAL (from OUTPUT section);
# $wantRETVAL set if 'RETVAL =' autogenerated
@($wantRETVAL, $ret_type) = @(0, 'void') if $RETVAL_no_return;
undef %outargs ;
process_keyword("POSTCALL|OUTPUT|ALIAS|ATTRS|PROTOTYPE");
&generate_output(%var_types{?$_}, %args_match{?$_}, $_, $DoSetMagic)
for grep { %in_out{?$_} =~ m/OUT$/ }, keys %in_out;
# all OUTPUT done, so now push the return value on the stack
if ($gotRETVAL && $RETVAL_code) {
print $output_fh, "\t$RETVAL_code\n";
} elsif ($gotRETVAL || $wantRETVAL) {
my $t = $WantOptimize && %targetable{?%type_kind{?$ret_type}};
my $var = 'RETVAL';
my $type = $ret_type;
# 0: type, 1: with_size, 2: how, 3: how_size
if ($t and not $t->[1] and $t->[0] eq 'p') {
# PUSHp corresponds to setpvn. Treate setpv directly
my $what = eval qq("$t->[2]");
warn $^EVAL_ERROR if $^EVAL_ERROR;
print $output_fh, "\tsv_setpv(TARG, $what); XSprePUSH; PUSHTARG;\n";
$prepush_done = 1;
}
elsif ($t) {
my $what = eval qq("$t->[2]");
warn $^EVAL_ERROR if $^EVAL_ERROR;
my $size = $t->[3];
$size = '' unless defined $size;
$size = eval qq("$size");
warn $^EVAL_ERROR if $^EVAL_ERROR;
print $output_fh, "\tXSprePUSH; PUSH$t->[0]($what$size);\n";
$prepush_done = 1;
}
else {
# RETVAL almost never needs SvSETMAGIC()
&generate_output($ret_type, 0, 'RETVAL', 0);
}
}
$xsreturn = 1 if $ret_type ne "void";
my $num = $xsreturn;
my $c = (nelems @outlist);
print $output_fh, "\tXSprePUSH;" if $c and not $prepush_done;
print $output_fh, "\tEXTEND(SP,$c);\n" if $c;
$xsreturn += $c;
generate_output(%var_types{?$_}, $num++, $_, 0, 1) for @outlist;
# do cleanup
process_keyword("CLEANUP|ALIAS|ATTRS|PROTOTYPE") ;
print $output_fh, Q(<<"EOF") if $ScopeThisXSUB;
# ]]
EOF
print $output_fh, Q(<<"EOF") if $ScopeThisXSUB and not $PPCODE;
# LEAVE;
EOF
# print function trailer
print $output_fh, Q(<<"EOF");
# ]]
EOF
print $output_fh, Q(<<"EOF") if $except;
# BEGHANDLERS
# CATCHALL
# sprintf(errbuf, "\%s: \%s\\tpropagated", Xname, Xreason);
# ENDHANDLERS
EOF
if (check_keyword("CASE")) {
blurt ("Error: No `CASE:' at top of function")
unless $condnum;
$_ = "CASE: $_"; # Restore CASE: label
next;
}
last if $_ eq "$END:";
death(m/^$BLOCK_re/o ?? "Misplaced `$1:'" !! "Junk at end of function");
}
print $output_fh, Q(<<"EOF") if $except;
# if (errbuf[0])
# Perl_croak(aTHX_ errbuf);
EOF
if ($xsreturn) {
print $output_fh, Q(<<"EOF") unless $PPCODE;
# XSRETURN($xsreturn);
EOF
} else {
print $output_fh, Q(<<"EOF") unless $PPCODE;
# XSRETURN_EMPTY;
EOF
}
print $output_fh, Q(<<"EOF");
#]]
#
EOF
my $newXS = "newXS" ;
my $proto = "" ;
# Build the prototype string for the xsub
if ($ProtoThisXSUB) {
$newXS = "newXSproto";
if ($ProtoThisXSUB eq 2) {
# User has specified empty prototype
}
elsif ($ProtoThisXSUB eq 1) {
my $s = ';';
if ($min_args +< $num_args) {
$s = '';
@proto_arg[$min_args] .= ";" ;
}
push @proto_arg, "$s\@"
if $ellipsis ;
$proto = join ("", grep { defined }, @proto_arg);
}
else {
# User has specified a prototype
$proto = $ProtoThisXSUB;
}
$proto = qq{, "$proto"};
}
if (%XsubAliases) {
%XsubAliases{+$pname} = 0
unless defined %XsubAliases{?$pname} ;
while ( @(?$name, ?$value) =@( each %XsubAliases)) {
push(@InitFileCode, Q(<<"EOF"));
# cv = newXS(\"$name\", XS_$Full_func_name, file);
# XSANY.any_i32 = $value ;
EOF
push(@InitFileCode, Q(<<"EOF")) if $proto;
# sv_setpv((SV*)cv$proto) ;
EOF
}
}
elsif ((nelems @Attributes)) {
push(@InitFileCode, Q(<<"EOF"));
# cv = newXS(\"$pname\", XS_$Full_func_name, file);
# apply_attrs_string("$Package", cv, "$(join ' ',@Attributes)", 0);
EOF
}
elsif ($interface) {
while ( @($name, $value) =@( each %Interfaces)) {
$name = "$Package\::$name" unless $name =~ m/::/;
push(@InitFileCode, Q(<<"EOF"));
# cv = newXS(\"$name\", XS_$Full_func_name, file);
# $interface_macro_set(cv,$value) ;
EOF
push(@InitFileCode, Q(<<"EOF")) if $proto;
# sv_setpv((SV*)cv$proto) ;
EOF
}
}
else {
push(@InitFileCode,
" CV* CV_$Full_func_name = $($newXS)(\"$pname\", XS_$Full_func_name, file$proto);\n"
. " sv_setiv(*av_fetch(svTav(SvLOCATION(CV_$Full_func_name)), 1, TRUE), $begin_lineno);\n");
if ($pname =~ m/::(BEGIN|INIT|UNITCHECK|CHECK|END)$/) {
my $keyword = $1;
push @InitFileCode,
" CvREFCNT_inc(CV_$Full_func_name);\n";
push @InitFileCode,
" process_special_block(Perl_keyword(aTHX_ STR_WITH_LEN(\"$keyword\")), CV_$Full_func_name);\n";
}
}
}
sub errors { $errors }
sub standard_typemap_locations {
# Add all the default typemap locations to the search path
my @tm = qw(typemap);
my $updir = File::Spec->updir;
foreach my $dir (@(File::Spec->catdir(< $: @($updir) x 1),
File::Spec->catdir(< $: @($updir) x 2),
File::Spec->catdir(< $: @($updir) x 3),
File::Spec->catdir(< $: @($updir) x 4))) {
unshift @tm, File::Spec->catfile($dir, 'typemap');
unshift @tm, File::Spec->catfile($dir, lib => ExtUtils => 'typemap');
}
foreach my $dir ( $^INCLUDE_PATH) {
my $file = File::Spec->catfile($dir, ExtUtils => 'typemap');
unshift @tm, $file if -e $file;
}
return @tm;
}
sub TrimWhitespace
{
@_[0] =~ s/^\s+|\s+$//go ;
}
sub TidyType {
local ($_) = @_[0] ;
# rationalise any '*' by joining them into bunches and removing whitespace
s#\s*(\*+)\s*#$1#g;
s#(\*+)# $1 #g ;
# change multiple whitespace into a single space
s/\s+/ /g ;
# trim leading & trailing whitespace
TrimWhitespace($_) ;
$_ ;
}
# Input: ($_, @line) == unparsed input.
# Output: ($_, @line) == (rest of line, following lines).
# Return: the matched keyword if found, otherwise 0
sub check_keyword {
$_ = shift(@line) while !m/\S/ && nelems @line;
s/^(\s*)(@_[0])\s*:\s*(?:#.*)?/$1/s && $2;
}
sub print_section {
my %args = %:< @_;
# the "do" is required for right semantics
{ $_ = shift(@line) } while !m/\S/ && nelems @line;
print($output_fh, "#line " . @line_no[((nelems @line_no) - (nelems @line) -1)] . " \"$filepathname\"\n")
if $WantLineNumbers && !m/^\s*#\s*line\b/ && !m/^#if XSubPPtmp/;
while (defined($_) && !m/^$BLOCK_re/) {
print $output_fh, "$_\n";
$_ = shift(@line);
}
print $output_fh, 'ExtUtils::ParseXS::CountLines'->end_marker . "\n" if $WantLineNumbers;
}
sub merge_section {
my $in = '';
while (!m/\S/ && nelems @line) {
$_ = shift(@line);
}
while (defined($_) && !m/^$BLOCK_re/) {
$in .= "$_\n";
$_ = shift(@line);
}
chomp $in;
return $in;
}
sub process_keyword($pattern)
{
my $kwd ;
&{*{Symbol::fetch_glob("$($kwd)_handler")}}()
while $kwd = check_keyword($pattern) ;
}
sub CASE_handler {
my %args = %:< @_;
blurt ("Error: `CASE:' after unconditional `CASE:'")
if $condnum && $cond eq '';
$cond = $_;
TrimWhitespace($cond);
print $output_fh, " " . ($condnum++ ?? " else" !! "") . ($cond ?? " if ($cond)\n" !! "\n");
$_ = '' ;
}
sub INPUT_handler {
while (!m/^$BLOCK_re/) {
last if m/^\s*NOT_IMPLEMENTED_YET/;
next unless m/\S/; # skip blank lines
TrimWhitespace($_) ;
my $line = $_ ;
# remove trailing semicolon if no initialisation
s/\s*;$//g unless m/[=;+].*\S/ ;
# Process the length(foo) declarations
if (s/^([^=]*)\blength\(\s*(\w+)\s*\)\s*$/$1 XSauto_length_of_$2=NO_INIT/x) {
print $output_fh, "\tSTRLEN\tSTRLEN_length_of_$2;\n";
%lengthof{+$2} = $name;
# $islengthof{$name} = $1;
$deferred .= "\n\tXSauto_length_of_$2 = STRLEN_length_of_$2;";
}
# check for optional initialisation code
my $var_init = '' ;
$var_init = $1 if s/\s*([=;+].*)$//s ;
$var_init =~ s/"/\\"/g;
s/\s+/ /g;
my @($var_type, $var_addr, $var_name) = @: m/^(.*?[^&\s])\s*(\&?)\s*\b(\w+)$/s
or blurt("Error: invalid argument declaration '$line'"), next;
# Check for duplicate definitions
blurt ("Error: duplicate definition of argument '$var_name' ignored"), next
if %arg_list{+$var_name}++
or defined %argtype_seen{?$var_name} and not $processing_arg_with_types;
$thisdone ^|^= $var_name eq "THIS";
$retvaldone ^|^= $var_name eq "RETVAL";
%var_types{+$var_name} = $var_type;
# XXXX This check is a safeguard against the unfinished conversion of
# generate_init(). When generate_init() is fixed,
# one can use 2-args map_type() unconditionally.
if ($var_type =~ m/ \( \s* \* \s* \) /x) {
# Function pointers are not yet supported with &output_init!
print $output_fh, "\t" . &map_type($var_type, $var_name);
$name_printed = 1;
} else {
print $output_fh, "\t" . &map_type($var_type);
$name_printed = 0;
}
$var_num = %args_match{?$var_name};
@proto_arg[$var_num] = ProtoString($var_type)
if $var_num ;
$func_args =~ s/\b($var_name)\b/&$1/ if $var_addr;
if ($var_init =~ m/^[=;]\s*NO_INIT\s*;?\s*$/
or %in_out{?$var_name} and %in_out{?$var_name} =~ m/^OUT/
and $var_init !~ m/\S/) {
if ($name_printed) {
print $output_fh, ";\n";
} else {
print $output_fh, "\t$var_name;\n";
}
} elsif ($var_init =~ m/\S/) {
&output_init($var_type, $var_num, $var_name, $var_init, $name_printed);
} elsif ($var_num) {
# generate initialization code
&generate_init($var_type, $var_num, $var_name, $name_printed);
} else {
print $output_fh, ";\n";
}
}
continue {
$_ = shift(@line);
}
}
sub OUTPUT_handler {
while (!m/^$BLOCK_re/) {
next unless m/\S/;
if (m/^\s*SETMAGIC\s*:\s*(ENABLE|DISABLE)\s*/) {
$DoSetMagic = ($1 eq "ENABLE" ?? 1 !! 0);
next;
}
my @($outarg, $outcode) = @: m/^\s*(\S+)\s*(.*?)\s*$/s ;
blurt ("Error: duplicate OUTPUT argument '$outarg' ignored"), next
if %outargs{+$outarg} ++ ;
if (!$gotRETVAL and $outarg eq 'RETVAL') {
# deal with RETVAL last
$RETVAL_code = $outcode ;
$gotRETVAL = 1 ;
next ;
}
blurt ("Error: OUTPUT $outarg not an argument"), next
unless defined(%args_match{?$outarg});
blurt("Error: No input definition for OUTPUT argument '$outarg' - ignored"), next
unless defined %var_types{?$outarg} ;
$var_num = %args_match{?$outarg};
if ($outcode) {
print $output_fh, "\t$outcode\n";
print $output_fh, "\tSvSETMAGIC(ST(" , $var_num-1 , "));\n" if $DoSetMagic;
} else {
&generate_output(%var_types{?$outarg}, $var_num, $outarg, $DoSetMagic);
}
delete %in_out{$outarg} # No need to auto-OUTPUT
if exists %in_out{$outarg} and %in_out{?$outarg} =~ m/OUT$/;
}
continue {
$_ = shift(@line);
}
}
sub C_ARGS_handler() {
my $in = merge_section();
TrimWhitespace($in);
$func_args = $in;
}
sub INTERFACE_MACRO_handler() {
my $in = merge_section();
TrimWhitespace($in);
if ($in =~ m/\s/) { # two
@($interface_macro, $interface_macro_set) = split ' ', $in;
} else {
$interface_macro = $in;
$interface_macro_set = 'UNKNOWN_CVT'; # catch later
}
$interface = 1; # local
$Interfaces = 1; # global
}
sub INTERFACE_handler() {
my $in = merge_section();
TrimWhitespace($in);
foreach (split m/[\s,]+/, $in) {
my $name = $_;
$name =~ s/^$Prefix//;
%Interfaces{+$name} = $_;
}
print $output_fh, Q(<<"EOF");
# XSFUNCTION = $interface_macro($ret_type,cv,XSANY.any_dptr);
EOF
$interface = 1; # local
$Interfaces = 1; # global
}
sub CLEANUP_handler() { print_section() }
sub PREINIT_handler() { print_section() }
sub POSTCALL_handler() { print_section() }
sub INIT_handler() { print_section() }
sub GetAliases($line)
{
my $orig = $line ;
my ($alias) ;
my ($value) ;
# Parse alias definitions
# format is
# alias = value alias = value ...
while ($line =~ s/^\s*([\w:]+)\s*=\s*(\w+)\s*//) {
$alias = $1 ;
$orig_alias = $alias ;
$value = $2 ;
# check for optional package definition in the alias
$alias = $Packprefix . $alias if $alias !~ m/::/ ;
# check for duplicate alias name & duplicate value
Warn("Warning: Ignoring duplicate alias '$orig_alias'")
if defined %XsubAliases{?$alias} ;
Warn("Warning: Aliases '$orig_alias' and '%XsubAliasValues{?$value}' have identical values")
if %XsubAliasValues{?$value} ;
$XsubAliases = 1;
%XsubAliases{+$alias} = $value ;
%XsubAliasValues{+$value} = $orig_alias ;
}
blurt("Error: Cannot parse ALIAS definitions from '$orig'")
if $line ;
}
sub ATTRS_handler ()
{
while (!m/^$BLOCK_re/) {
next unless m/\S/;
TrimWhitespace($_) ;
push @Attributes, $_;
}
continue {
$_ = shift(@line);
}
}
sub ALIAS_handler ()
{
while (!m/^$BLOCK_re/) {
next unless m/\S/;
TrimWhitespace($_) ;
GetAliases($_) if $_ ;
}
continue {
$_ = shift(@line);
}
}
sub FALLBACK_handler()
{
# the rest of the current line should contain either TRUE,
# FALSE or UNDEF
TrimWhitespace($_) ;
my %map = %(
TRUE => "&PL_sv_yes", 1 => "&PL_sv_yes",
FALSE => "&PL_sv_no", 0 => "&PL_sv_no",
UNDEF => "&PL_sv_undef",
) ;
# check for valid FALLBACK value
death ("Error: FALLBACK: TRUE/FALSE/UNDEF") unless exists %map{uc $_} ;
$Fallback = %map{?uc $_} ;
}
sub REQUIRE_handler ()
{
# the rest of the current line should contain a version number
my $Ver = $_ ;
TrimWhitespace($Ver) ;
death ("Error: REQUIRE expects a version number")
unless $Ver ;
# check that the version number is of the form n.n
death ("Error: REQUIRE: expected a number, got '$Ver'")
unless $Ver =~ m/^\d+(\.\d*)?/ ;
death ("Error: xsubpp $Ver (or better) required--this is only $VERSION.")
unless $VERSION +>= $Ver ;
}
sub VERSIONCHECK_handler ()
{
# the rest of the current line should contain either ENABLE or
# DISABLE
TrimWhitespace($_) ;
# check for ENABLE/DISABLE
death ("Error: VERSIONCHECK: ENABLE/DISABLE")
unless m/^(ENABLE|DISABLE)/i ;
$WantVersionChk = 1 if $1 eq 'ENABLE' ;
$WantVersionChk = 0 if $1 eq 'DISABLE' ;
}
sub PROTOTYPE_handler ()
{
my $specified ;
death("Error: Only 1 PROTOTYPE definition allowed per xsub")
if $proto_in_this_xsub ++ ;
while (!m/^$BLOCK_re/) {
next unless m/\S/;
$specified = 1 ;
TrimWhitespace($_) ;
if ($_ eq 'DISABLE') {
$ProtoThisXSUB = 0
} elsif ($_ eq 'ENABLE') {
$ProtoThisXSUB = 1
} else {
# remove any whitespace
s/\s+//g ;
death("Error: Invalid prototype '$_'")
unless ValidProtoString($_) ;
$ProtoThisXSUB = C_string($_) ;
}
}
continue {
$_ = shift(@line);
}
# If no prototype specified, then assume empty prototype ""
$ProtoThisXSUB = 2 unless $specified ;
$ProtoUsed = 1 ;
}
sub SCOPE_handler ()
{
death("Error: Only 1 SCOPE declaration allowed per xsub")
if $scope_in_this_xsub ++ ;
while (!m/^$BLOCK_re/) {
next unless m/\S/;
TrimWhitespace($_) ;
if ($_ =~ m/^DISABLE/i) {
$ScopeThisXSUB = 0
} elsif ($_ =~ m/^ENABLE/i) {
$ScopeThisXSUB = 1
}
}
continue {
$_ = shift(@line);
}
}
sub PROTOTYPES_handler ()
{
# the rest of the current line should contain either ENABLE or
# DISABLE
TrimWhitespace($_) ;
# check for ENABLE/DISABLE
death ("Error: PROTOTYPES: ENABLE/DISABLE")
unless m/^(ENABLE|DISABLE)/i ;
$WantPrototypes = 1 if $1 eq 'ENABLE' ;
$WantPrototypes = 0 if $1 eq 'DISABLE' ;
$ProtoUsed = 1 ;
}
sub INCLUDE_handler ()
{
# the rest of the current line should contain a valid filename
TrimWhitespace($_) ;
death("INCLUDE: filename missing")
unless $_ ;
death("INCLUDE: output pipe is illegal")
if m/^\s*\|/ ;
# simple minded recursion detector
death("INCLUDE loop detected")
if %IncludedFiles{?$_} ;
++ %IncludedFiles{+$_} unless m/\|\s*$/ ;
# Save the current file context.
push(@XSStack, \%(
type => 'file',
LastLine => $lastline,
LastLineNo => $lastline_no,
Line => \@line,
LineNo => \@line_no,
Filename => $filename,
Filepathname => $filepathname,
Handle => $FH,
)) ;
$FH = Symbol::gensym();
# open the new file
open ($FH, "<", "$_") or death("Cannot open '$_': $^OS_ERROR") ;
print $output_fh, Q(<<"EOF");
#
#/* INCLUDE: Including '$_' from '$filename' */
#
EOF
$filepathname = $filename = $_ ;
# Prime the pump by reading the first
# non-blank line
# skip leading blank lines
my $line;
while ($line = ~< $FH) {
last unless $line =~ m/^\s*$/ ;
}
$lastline = $line ;
$lastline_no = iohandle::input_line_number($FH);
}
sub PopFile()
{
return 0 unless @XSStack[-1]->{?type} eq 'file' ;
my $data = pop @XSStack ;
my $ThisFile = $filename ;
my $isPipe = ($filename =~ m/\|\s*$/) ;
-- %IncludedFiles{+$filename}
unless $isPipe ;
close $FH ;
$FH = $data->{?Handle} ;
# $filename is the leafname, which for some reason isused for diagnostic
# messages, whereas $filepathname is the full pathname, and is used for
# #line directives.
$filename = $data->{?Filename} ;
$filepathname = $data->{?Filepathname} ;
$lastline = $data->{?LastLine} ;
$lastline_no = $data->{?LastLineNo} ;
@line = @{ $data->{?Line} } ;
@line_no = @{ $data->{?LineNo} } ;
if ($isPipe and $^CHILD_ERROR ) {
-- $lastline_no ;
print $^STDERR, "Error reading from pipe '$ThisFile': $^OS_ERROR in $filename, line $lastline_no\n" ;
exit 1 ;
}
print $output_fh, Q(<<"EOF");
#
#/* INCLUDE: Returning to '$filename' from '$ThisFile' */
#
EOF
return 1 ;
}
sub ValidProtoString($string)
{
if ( $string =~ m/^$proto_re+$/ ) {
return $string ;
}
return 0 ;
}
sub C_string($string)
{
$string =~ s[\\][\\\\]g ;
$string ;
}
sub ProtoString($type)
{
%proto_letter{?$type} or "\$" ;
}
sub check_cpp {
my @cpp = grep( {m/^\#\s*(?:if|e\w+)/ }, @line);
if ((nelems @cpp)) {
my ($cpplevel);
for my $cpp ( @cpp) {
if ($cpp =~ m/^\#\s*if/) {
$cpplevel++;
} elsif (!$cpplevel) {
Warn("Warning: #else/elif/endif without #if in this function");
print $^STDERR, " (precede it with a blank line if the matching #if is outside the function)\n"
if @XSStack[-1]->{?type} eq 'if';
return;
} elsif ($cpp =~ m/^\#\s*endif/) {
$cpplevel--;
}
}
Warn("Warning: #if without #endif in this function") if $cpplevel;
}
}
sub Q($text) {
$text =~ s/^#//gm;
$text =~ s/\[\[/\{/g;
$text =~ s/\]\]/\}/g;
$text;
}
# Read next xsub into @line from ($lastline, <$FH>).
sub fetch_para {
# parse paragraph
death ("Error: Unterminated `#if/#ifdef/#ifndef'")
if !defined $lastline && @XSStack[-1]->{?type} eq 'if';
@line = @( () );
@line_no = @( () ) ;
return PopFile() if !defined $lastline;
if ($lastline =~
m/^MODULE\s*=\s*([\w:]+)(?:\s+PACKAGE\s*=\s*([\w:]+))?(?:\s+PREFIX\s*=\s*(\S+))?\s*$/) {
$Module = $1;
$Package = defined($2) ?? $2 !! ''; # keep -w happy
$Prefix = defined($3) ?? $3 !! ''; # keep -w happy
$Prefix = quotemeta $Prefix ;
($Module_cname = $Module) =~ s/\W/_/g;
($Packid = $Package) =~ s/:/_/g;
$Packprefix = $Package;
$Packprefix .= "::" if $Packprefix ne "";
$lastline = "";
}
while (1) {
# Skip embedded PODs
while ($lastline =~ m/^=/) {
while ($lastline = ~< $FH) {
last if ($lastline =~ m/^=cut\s*$/);
}
death ("Error: Unterminated pod") unless $lastline;
$lastline = ~< $FH;
chomp $lastline;
$lastline =~ s/^\s+$//;
}
if ($lastline !~ m/^\s*#/ ||
# CPP directives:
# ANSI: if ifdef ifndef elif else endif define undef
# line error pragma
# gcc: warning include_next
# obj-c: import
# others: ident (gcc notes that some cpps have this one)
$lastline =~ m/^#[ \t]*(?:(?:if|ifn?def|elif|else|endif|define|undef|pragma|error|warning|line\s+\d+|ident)\b|(?:include(?:_next)?|import)\s*["<].*[>"])/) {
last if $lastline =~ m/^\S/ && nelems @line && @line[-1] eq "";
push(@line, $lastline);
push(@line_no, $lastline_no) ;
}
# Read next line and continuation lines
last unless defined($lastline = ~< $FH);
$lastline_no = iohandle::input_line_number($FH);
my $tmp_line;
$lastline .= $tmp_line
while ($lastline =~ m/\\$/ && defined($tmp_line = ~< $FH));
chomp $lastline;
$lastline =~ s/^\s+$//;
}
pop(@line), pop(@line_no) while (nelems @line) && @line[-1] eq "";
1;
}
sub output_init {
local ($type, $num, $var, $init, $name_printed);
@($type, $num, $var, $init, $name_printed) = @_;
local ($arg) = "ST(" . ($num - 1) . ")";
if ( $init =~ m/^=/ ) {
my $x_init = evalqq($init);
if ($name_printed) {
print $output_fh, " $x_init\n";
} else {
my $x_var = evalqq($var);
print $output_fh, "\t$x_var $x_init\n";
}
} else {
if ( $init =~ s/^\+// && $num ) {
&generate_init($type, $num, $var, $name_printed);
} elsif ($name_printed) {
print $output_fh, ";\n";
$init =~ s/^;//;
} else {
my $x_var = evalqq($var);
print $output_fh, "\t$x_var;\n";
$init =~ s/^;//;
}
my $x_init = evalqq($init);
$deferred .= "\n\t$x_init\n";
}
}
sub Warn
{
# work out the line number
my $line_no = @line_no[(nelems @line_no) - (nelems @line) -1] ;
print $^STDERR, "$(join ' ',@_) in $filename, line $line_no\n" ;
}
sub blurt
{
Warn < @_ ;
$errors ++
}
sub death
{
Warn < @_ ;
die < @_;
}
sub evalqq {
my $x = shift;
my $ex = eval qq/"$x"/;
die "error in '$x': $($^EVAL_ERROR->message)" if $^EVAL_ERROR;
return $ex;
}
sub generate_init {
local@($type, $num, $var, ...) = @_;
local($arg) = "ST(" . ($num - 1) . ")";
local($argoff) = $num - 1;
local($ntype);
local($tk);
$type = TidyType($type) ;
blurt("Error: '$type' not in typemap"), return
unless defined(%type_kind{?$type});
($ntype = $type) =~ s/\s*\*/Ptr/g;
($subtype = $ntype) =~ s/(?:Array)?(?:Ptr)?$//;
$tk = %type_kind{?$type};
$tk =~ s/OBJ$/REF/ if $func_name =~ m/DESTROY$/;
if ($tk eq 'T_PV' and exists %lengthof{$var}) {
print $output_fh, "\t$var" unless $name_printed;
print $output_fh, " = ($type)SvPV($arg, STRLEN_length_of_$var);\n";
die "default value not supported with length(NAME) supplied"
if defined %defaults{?$var};
return;
}
$type =~ s/:/_/g unless $hiertype;
blurt("Error: No INPUT definition for type '$type', typekind '%type_kind{?$type}' found"), return
unless defined %input_expr{?$tk} ;
$expr = %input_expr{?$tk};
if ($expr =~ m/DO_ARRAY_ELEM/) {
blurt("Error: '$subtype' not in typemap"), return
unless defined(%type_kind{?$subtype});
blurt("Error: No INPUT definition for type '$subtype', typekind '%type_kind{?$subtype}' found"), return
unless defined %input_expr{?%type_kind{?$subtype}} ;
$subexpr = %input_expr{?%type_kind{?$subtype}};
$subexpr =~ s/\$type/\$subtype/g;
$subexpr =~ s/ntype/subtype/g;
$subexpr =~ s/\$arg/ST(ix_$var)/g;
$subexpr =~ s/\n\t/\n\t\t/g;
$subexpr =~ s/is not of (.*\")/[arg \%d] is not of $1, ix_$var + 1/g;
$subexpr =~ s/\$var/$($var)\[ix_$var - $argoff]/;
$expr =~ s/DO_ARRAY_ELEM/$subexpr/;
}
if ($expr =~ m#/\*.*scope.*\*/#i) { # "scope" in C comments
$ScopeThisXSUB = 1;
}
my $x_var = evalqq($var);
if (defined(%defaults{?$var})) {
$expr =~ s/(\t+)/$1 /g;
$expr =~ s/ /\t/g;
if ($name_printed) {
print $output_fh, ";\n";
} else {
print $output_fh, "\t$x_var;\n";
}
my $x_num = evalqq($num);
my $x_expr = evalqq($expr);
if (%defaults{?$var} eq 'NO_INIT') {
$deferred .= qq/\n\tif (items >= $x_num) \{\n$x_expr;\n\t\}\n/;
} else {
my $x_defaults_var = evalqq(%defaults{?$var});
$deferred .= qq/\n\tif (items < $x_num)\n\t $x_var = $x_defaults_var;\n\telse \{\n$x_expr;\n\t\}\n/;
}
} elsif ($ScopeThisXSUB or $expr !~ m/^\s*\$var =/) {
if ($name_printed) {
print $output_fh, ";\n";
} else {
print $output_fh, "\t$x_var;\n";
}
my $x_expr = evalqq($expr);
$deferred .= "\n$x_expr;\n";
} else {
die "panic: do not know how to handle this branch for function pointers"
if $name_printed;
my $x_expr = evalqq($expr);
print $output_fh, "$x_expr;\n";
}
}
sub generate_output {
local@($type, $num, $var, $do_setmagic, ?$do_push) = @_;
local($arg) = "ST(" . ($num - ($num != 0)) . ")";
local($argoff) = $num - 1;
local($ntype);
$type = TidyType($type) ;
if ($type =~ m/^array\(([^,]*),(.*)\)/) {
print $output_fh, "\t$arg = sv_newmortal();\n";
print $output_fh, "\tsv_setpvn($arg, (char *)$var, $2 * sizeof($1));\n";
print $output_fh, "\tSvSETMAGIC($arg);\n" if $do_setmagic;
} else {
blurt("Error: '$type' not in typemap"), return
unless defined(%type_kind{?$type});
blurt("Error: No OUTPUT definition for type '$type', typekind '%type_kind{?$type}' found"), return
unless defined %output_expr{?%type_kind{?$type}} ;
($ntype = $type) =~ s/\s*\*/Ptr/g;
$ntype =~ s/\(\)//g;
($subtype = $ntype) =~ s/(?:Array)?(?:Ptr)?$//;
$expr = %output_expr{?%type_kind{?$type}};
if ($expr =~ m/DO_ARRAY_ELEM/) {
blurt("Error: '$subtype' not in typemap"), return
unless defined(%type_kind{?$subtype});
blurt("Error: No OUTPUT definition for type '$subtype', typekind '%type_kind{?$subtype}' found"), return
unless defined %output_expr{?%type_kind{?$subtype}} ;
$subexpr = %output_expr{?%type_kind{?$subtype}};
$subexpr =~ s/ntype/subtype/g;
$subexpr =~ s/\$arg/tmp_$var/g;
$subexpr =~ s/\$var/$($var)\[ix_$var]/g;
$subexpr =~ s/\n\t/\n\t\t/g;
$expr =~ s/DO_ARRAY_ELEM\n/$subexpr/;
eval "print \$output_fh, qq\a$expr\a";
warn $^EVAL_ERROR if $^EVAL_ERROR;
print $output_fh, "\t\tSvSETMAGIC(ST(ix_$var));\n" if $do_setmagic;
} elsif ($var eq 'RETVAL') {
if ($expr =~ m/^\t\$arg = new/) {
# We expect that $arg has refcnt 1, so we need to
# mortalize it.
eval "print \$output_fh, qq\a$expr\a";
warn $^EVAL_ERROR if $^EVAL_ERROR;
print $output_fh, "\tsv_2mortal(ST($num));\n";
print $output_fh, "\tSvSETMAGIC(ST($num));\n" if $do_setmagic;
} elsif ($expr =~ m/^\s*\$arg\s*=/) {
# We expect that $arg has refcnt >=1, so we need
# to mortalize it!
eval "print \$output_fh, qq\a$expr\a";
warn $^EVAL_ERROR if $^EVAL_ERROR;
print $output_fh, "\tsv_2mortal(ST(0));\n";
print $output_fh, "\tSvSETMAGIC(ST(0));\n" if $do_setmagic;
} else {
# Just hope that the entry would safely write it
# over an already mortalized value. By
# coincidence, something like $arg = &sv_undef
# works too.
print $output_fh, "\tST(0) = sv_newmortal();\n";
eval "print \$output_fh, qq\a$expr\a";
warn $^EVAL_ERROR if $^EVAL_ERROR;
# new mortals don't have set magic
}
} elsif ($do_push) {
print $output_fh, "\tPUSHs(sv_newmortal());\n";
$arg = "ST($num)";
eval "print \$output_fh, qq\a$expr\a";
warn $^EVAL_ERROR if $^EVAL_ERROR;
print $output_fh, "\tSvSETMAGIC($arg);\n" if $do_setmagic;
} elsif ($arg =~ m/^ST\(\d+\)$/) {
eval "print \$output_fh, qq\a$expr\a";
warn $^EVAL_ERROR if $^EVAL_ERROR;
print $output_fh, "\tSvSETMAGIC($arg);\n" if $do_setmagic;
}
}
}
sub map_type($type, ?$varname) {
# C++ has :: in types too so skip this
$type =~ s/:/_/g unless $hiertype;
$type =~ s/^array\(([^,]*),(.*)\).*/$1 */s;
if ($varname) {
if ($varname && $type =~ m/ \( \s* \* (?= \s* \) ) /xg) {
substr $type, pos($type), 0, " $varname ";
} else {
$type .= "\t$varname";
}
}
$type;
}
#########################################################
package
ExtUtils::ParseXS::CountLines;
our ($SECTION_END_MARKER);
our $cfile;
sub PUSHED($class, $mode, $fh) {
my $mcfile = $cfile;
$mcfile =~ s/\\/\\\\/g;
$SECTION_END_MARKER = qq{#line --- "$mcfile"};
return bless \%(buffer => '',
fh => $fh,
line_no => 1,
), $class;
}
sub WRITE($self,$buf,$fh) {
my $length = 0;
$self->{+buffer} .= $buf;
while ($self->{+buffer} =~ s/^([^\n]*\n)//) {
my $line = $1;
$length += length($line);
++ $self->{+line_no};
$line =~ s|^\#line\s+---(?=\s)|#line $self->{?line_no}|;
print $self->{?fh}, $line;
}
return $length;
}
sub FLUSH($self,$fh) {
print $fh, $self->{?buffer} or return -1;
$self->{+buffer} = '';
return 0;
}
sub end_marker {
return $SECTION_END_MARKER;
}
1;
__END__
=head1 NAME
ExtUtils::ParseXS - converts Perl XS code into C code
=head1 SYNOPSIS
use ExtUtils::ParseXS qw(process_file);
process_file( filename => 'foo.xs' );
process_file( filename => 'foo.xs',
output => 'bar.c',
'C++' => 1,
typemap => 'path/to/typemap',
hiertype => 1,
except => 1,
prototypes => 1,
versioncheck => 1,
linenumbers => 1,
optimize => 1,
prototypes => 1,
);
=head1 DESCRIPTION
C<ExtUtils::ParseXS> 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<typemap>. It will use
the following search path to find default typemaps, with the rightmost
typemap taking precedence.
../../../typemap:../../typemap:../typemap:typemap
=head1 EXPORT
None by default. C<process_file()> may be exported upon request.
=head1 FUNCTIONS
=over 4
=item process_xs()
This function processes an XS file and sends output to a C file.
Named parameters control how the processing is done. The following
parameters are accepted:
=over 4
=item B<C++>
Adds C<extern "C"> to the C code. Default is false.
=item B<hiertype>
Retains C<::> in type names so that C++ hierachical types can be
mapped. Default is false.
=item B<except>
Adds exception handling stubs to the C code. Default is false.
=item B<typemap>
Indicates that a user-supplied typemap should take precedence over the
default typemaps. A single typemap may be specified as a string, or
multiple typemaps can be specified in an array reference, with the
last typemap having the highest precedence.
=item B<prototypes>
Generates prototype code for all xsubs. Default is false.
=item B<versioncheck>
Makes sure at run time that the object file (derived from the C<.xs>
file) and the C<.pm> files have the same version number. Default is
true.
=item B<linenumbers>
Adds C<#line> directives to the C output so error messages will look
like they came from the original XS file. Default is true.
=item B<optimize>
Enables certain optimizations. The only optimization that is currently
affected is the use of I<target>s by the output C code (see L<perlguts>).
Not optimizing may significantly slow down the generated code, but this is the way
B<xsubpp> of 5.005 and earlier operated. Default is to optimize.
=item B<inout>
Enable recognition of C<IN>, C<OUT_LIST> and C<INOUT_LIST>
declarations. Default is true.
=item B<argtypes>
Enable recognition of ANSI-like descriptions of function signature.
Default is true.
=item B<s>
I have no clue what this does. Strips function prefixes?
=back
=item errors()
This function returns the number of [a certain kind of] errors
encountered during processing of the XS file.
=back
=head1 AUTHOR
Based on xsubpp code, written by Larry Wall.
Maintained by Ken Williams, <ken@mathforum.org>
=head1 COPYRIGHT
Copyright 2002-2003 Ken Williams. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
Based on the ExtUtils::xsubpp code by Larry Wall and the Perl 5
Porters, which was released under the same license terms.
=head1 SEE ALSO
L<perl>, ExtUtils::xsubpp, ExtUtils::MakeMaker, L<perlxs>, L<perlxstut>.
=cut