package ExtUtils::ParseXS;
use strict;
use warnings;

# Note that the pod for this module is separate in ParseXS.pod.
#
# This module provides the guts for the xsubpp XS-to-C translator utility.
# By having it as a module separate from xsubpp, it makes it more efficient
# to be used for example by Module::Build without having to shell out to
# xsubpp. It also makes it easier to test the individual components.
#
# The bulk of this file is taken up with the process_file() method which
# does the whole job of reading in a .xs file and outputting a .c file.
# It in turn relies on fetch_para() to read chunks of lines from the
# input, and on a bunch of FOO_handler() methods which process each of the
# main XS FOO keywords when encountered.
#
# The remainder of this file mainly consists of helper functions for the
# handlers, and functions to help with outputting stuff.
#
# Of particular note is the Q() function, which is typically used to
# process escaped ("quoted") heredoc text of C code fragments to be
# output. It strips an initial '|' preceded by optional spaces, and
# converts [[ and ]] to { and }.  This allows unmatched braces to be
# included in the C fragments without confusing text editors.
#
# Some other tasks have been moved out to various .pm files under ParseXS:
#
# ParseXS::CountLines   provides tied handle methods for automatically
#                       injecting '#line' directives into output.
#
# ParseXS::Eval         provides methods for evalling typemaps within
#                       an environment where suitable vars like $var and
#                       $arg have been up, but with nothing else in scope.
#
# ParseXS::Node         This and its subclasses provide the nodes
#                       which make up the Abstract Syntax Tree (AST)
#                       generated by the parser. XXX as of Sep 2024, this
#                       is very much a Work In Progress.
#
# ParseXS::Constants    defines a few constants used here, such the regex
#                       patterns used to detect a new XS keyword.
#
# ParseXS::Utilities    provides various private utility methods for
#                       the use of ParseXS, such as analysing C
#                       pre-processor directives.
#
# Note: when making changes to this module (or to its children), you
# can make use of the author/mksnapshot.pl tool to capture before and
# after snapshots of all .c files generated from .xs files (e.g. all the
# ones generated when building the perl distribution), to make sure that
# the only the changes to have appeared are ones which you expected.

# 5.8.0 is required for "use fields"
# 5.8.3 is required for "use Exporter 'import'"
use 5.008003;

use Cwd;
use Config;
use Exporter 'import';
use File::Basename;
use File::Spec;
use Symbol;

our $VERSION;
BEGIN {
  $VERSION = '3.57';
  require ExtUtils::ParseXS::Constants; ExtUtils::ParseXS::Constants->VERSION($VERSION);
  require ExtUtils::ParseXS::CountLines; ExtUtils::ParseXS::CountLines->VERSION($VERSION);
  require ExtUtils::ParseXS::Node; ExtUtils::ParseXS::Node->VERSION($VERSION);
  require ExtUtils::ParseXS::Utilities; ExtUtils::ParseXS::Utilities->VERSION($VERSION);
  require ExtUtils::ParseXS::Eval; ExtUtils::ParseXS::Eval->VERSION($VERSION);
}
$VERSION = eval $VERSION if $VERSION =~ /_/;

use ExtUtils::ParseXS::Utilities qw(
  standard_typemap_locations
  trim_whitespace
  C_string
  valid_proto_string
  process_typemaps
  map_type
  standard_XS_defs
  analyze_preprocessor_statement
  set_cond
  Warn
  WarnHint
  current_line_number
  blurt
  death
  check_conditional_preprocessor_statements
  escape_file_for_line_directive
  report_typemap_failure
);

our @EXPORT_OK = qw(
  process_file
  report_error_count
  errors
);

##############################
# A number of "constants"
our $DIE_ON_ERROR;

our $AUTHOR_WARNINGS;
$AUTHOR_WARNINGS = ($ENV{AUTHOR_WARNINGS} || 0)
    unless defined $AUTHOR_WARNINGS;

# "impossible" keyword (multiple newline)
my $END = "!End!\n\n";
# Match an XS Keyword
my $BLOCK_regexp = '\s*(' . $ExtUtils::ParseXS::Constants::XSKeywordsAlternation . "|$END)\\s*:";


# All the valid fields of an ExtUtils::ParseXS hash object. The 'use
# fields' enables compile-time or run-time errors if code attempts to
# use a key which isn't listed here.

my $USING_FIELDS;

BEGIN {
  my @fields = (

  # I/O:

  'dir',                # The directory component of the main input file:
                        # we will normally chdir() to this directory.

  'in_pathname',        # The full pathname of the current input file.
  'in_filename',        # The filename      of the current input file.
  'in_fh',              # The filehandle    of the current input file.

  'IncludedFiles',      # Bool hash of INCLUDEd filenames (plus main file).

  'line',               # Array of lines recently read in and being processed.
                        # Typically one XSUB's worth of lines.
  'line_no',            # Array of line nums corresponding to @{$self->{line}}.

  'lastline',           # The contents of the line most recently read in
                        # but not yet processed.
  'lastline_no',        # The line number of lastline.


  # File-scoped configuration state:

  'config_RetainCplusplusHierarchicalTypes', # Bool: "-hiertype" switch
                        # value: it stops the typemap code doing
                        # $type =~ tr/:/_/.

  'config_WantLineNumbers', # Bool: (default true): "-nolinenumbers"
                        # switch not present: causes '#line NNN' lines to
                        # be emitted.

  'config_die_on_error',# Bool: make death() call die() rather than exit().
                        # It is set initially from the die_on_error option
                        # or from the $ExtUtils::ParseXS::DIE_ON_ERROR global.

  'config_author_warnings', # Bool: enables some warnings only useful to
                        # ParseXS.pm's authors rather than module creators.
                        # Set from Options or $AUTHOR_WARNINGS env var.

  'config_strip_c_func_prefix', # The discouraged -strip=... switch.

  'config_allow_argtypes', # Bool: (default true): "-noargtypes" switch not
                        # present. Enables ANSI-like arg types to be
                        # included in the XSUB signature.

  'config_allow_inout', # Bool: (default true): "-noinout" switch not present.
                        # Enables processing of IN/OUT/etc arg modifiers.

  'config_allow_exceptions', # Bool: (default false): the '-except' switch
                        # present.

  'config_optimize',    # Bool: (default true): "-nooptimize" switch not
                        # present. Enables optimizations (currently just
                        # the TARG one).


  # File-scoped parsing state:

  'typemaps_object',    # An ExtUtils::Typemaps object: the result of
                        # reading in the standard (or other) typemap.

  'error_count',        # Num: count of number of errors seen so far.

  'XS_parse_stack',     # Array of hashes: nested INCLUDE and #if states.

  'XS_parse_stack_top_if_idx', # Index of the current top-most '#if' on the
                        # XS_parse_stack. Note that it's not necessarily
                        # the top element of the stack, since that also
                        # includes elements for each INCLUDE etc.

  'MODULE_cname',       # MODULE canonical name (i.e. after s/\W/_/g).
  'PACKAGE_name',       # PACKAGE name.
  'PACKAGE_C_name',     #             Ditto, but with tr/:/_/.
  'PACKAGE_class',      #             Ditto, but with '::' appended.
  'PREFIX_pattern',     # PREFIX value, but after quotemeta().

  'map_overloaded_package_to_C_package', # Hash: for every PACKAGE which
                        # has at least one overloaded XSUB, add a
                        # (package name => package C name) entry.

  'map_package_to_fallback_string', # Hash: for every package, maps it to
                        # the overload fallback state for that package (if
                        # specified). Each value is one of the strings
                        # "&PL_sv_yes", "&PL_sv_no", "&PL_sv_undef".

  'proto_behaviour_specified', # Bool: prototype behaviour has been
                        # specified by the -prototypes switch and/or
                        # PROTOTYPE(S) keywords, so no need to warn.

  'PROTOTYPES_value',   # Bool: most recent PROTOTYPES: value. Defaults to
                        # the value of the "-prototypes" switch.

  'VERSIONCHECK_value', # Bool: most recent VERSIONCHECK: value. Defaults
                        # to the value of the "-noversioncheck" switch.

  'seen_INTERFACE_or_MACRO', # Bool: at least one INTERFACE/INTERFACE_MACRO
                        # has been seen somewhere.


  # File-scoped code-emitting state:

  'bootcode_early',     # Array of code lines to emit early in boot XSUB:
                        # typically newXS() calls

  'bootcode_later',     # Array of code lines to emit later on in boot XSUB:
                        # typically lines from a BOOT: XS file section


  # Per-XSUB parsing state:

  'xsub_seen_NO_OUTPUT',       # Bool: XSUB declared as NO_OUTPUT

  'xsub_seen_extern_C',        # Bool: XSUB return type is 'extern "C" ...'

  'xsub_seen_static',          # Bool: XSUB return type is 'static ...'

  'xsub_seen_PPCODE',          # Bool: XSUB has PPCODE    (peek-ahead)

  'xsub_seen_CODE',            # Bool: XSUB has CODE      (peek-ahead)

  'xsub_seen_INTERFACE',       # Bool: XSUB has INTERFACE (peek-ahead)

  'xsub_seen_PROTOTYPE',       # Bool: PROTOTYPE keyword seen (for dup warning)
  
  'xsub_seen_SCOPE',           # Bool: SCOPE keyword seen (for dup warning).
  
  'xsub_seen_ALIAS',           # Bool: ALIAS keyword seen in this XSUB.

  'xsub_seen_INTERFACE_or_MACRO',# Bool: INTERFACE or INTERFACE_MACRO
                               # seen in this XSUB.
  
  'xsub_interface_macro',      # Str: current interface extraction macro.
  
  'xsub_interface_macro_set',  # Str: current interface setting macro.
  
  'xsub_prototype',            # Str: is set to either the global PROTOTYPES
                               #  values (0 or 1), or to what's been
                               #  overridden for this XSUB with PROTOTYPE
                               #    "0": DISABLE
                               #    "1": ENABLE
                               #    "2": empty prototype
                               #    other: a specific prototype.

  'xsub_SCOPE_enabled',        # Bool: SCOPE ENABLEd

  'xsub_return_type',          # Return type of the XSUB (whitespace-tidied).

  'xsub_class',                # Str: the class part of the XSUB's
                               # function name (if any). May include
                               # 'const' prefix.

  'xsub_sig',                  # Node::Sig object holding all the info
                               # about the XSUB's signature and INPUT
                               # lines

  'xsub_func_name',            # The name of this XSUB        eg 'f'
  'xsub_func_full_perl_name',  # its full Perl function name  eg. 'Foo::Bar::f'
  'xsub_func_full_C_name',     # its full C function name     eg 'Foo__Bar__f'

  'xsub_CASE_condition',       # Most recent CASE string.

  'xsub_CASE_condition_count', # number of CASE keywords encountered.
                               # Zero indicates none encountered yet.

  'xsub_map_overload_name_to_seen', # Hash: maps each overload method name
                               # (such as '<=>') to a boolean indicating
                               # whether that method has been listed by
                               # OVERLOAD (for duplicate spotting).
   
  'xsub_map_interface_name_short_to_original', # Hash: for each INTERFACE
                               # name, map the short (PREFIX removed) name
                               # to the original name.

  'xsub_attributes',           # Array of strings: all ATTRIBUTE keywords
                               # (possibly multiple space-separated
                               # keywords per string).

  'xsub_seen_RETVAL_in_CODE',  # Have seen 'RETVAL' within a CODE block.

  'xsub_map_alias_name_to_value', # Hash: maps ALIAS name to value.

  'xsub_map_alias_value_to_name_seen_hash', # Hash of hash of bools:
                               # indicates which alias names have been
                               # used for each value.

  'xsub_alias_clash_hinted',   # Bool: an ALIAS warning-hint has been emitted.


  # Per-XSUB OUTPUT section parsing state:

  'xsub_SETMAGIC_state',       # Bool: most recent value of SETMAGIC in an
                               # OUTPUT section.

  # Per-XSUB code-emitting state:

  'xsub_deferred_code_lines',  # A multi-line string containing lines of
                               # code to be emitted *after* all INPUT and
                               # PREINIT keywords have been processed.

  'xsub_stack_was_reset',      # An XSprePUSH was emitted, so return values
                               # should be PUSHed rather than just set.

  'xsub_targ_declared_early',  # A wide-scoped dXSTARG was emitted early
  'xsub_targ_used',            # The TARG has already been used

  );

  # do 'use fields', except: fields needs Hash::Util which is XS, which
  # needs us. So only 'use fields' on systems where Hash::Util has already
  # been built.
  if (eval 'require Hash::Util; 1;') {
    require fields;
    $USING_FIELDS = 1;
    fields->import(@fields);
  }
}


sub new {
  my ExtUtils::ParseXS $self = shift;
  unless (ref $self) {
      if ($USING_FIELDS) {
        $self = fields::new($self);
      }
      else {
        $self = bless {} => $self;
      }
  }
  return $self;
}

our $Singleton = __PACKAGE__->new;


# The big method which does all the input parsing and output generation

sub process_file {
  my ExtUtils::ParseXS $self;
  # Allow for $package->process_file(%hash), $obj->process_file, and process_file()
  if (@_ % 2) {
    my $invocant = shift;
    $self = ref($invocant) ? $invocant : $invocant->new;
  }
  else {
    $self = $Singleton;
  }

  my %Options;

  {
    my %opts = @_;
    $self->{proto_behaviour_specified} = exists $opts{prototypes};

    # Set defaults.
    %Options = (
      argtypes        => 1,
      csuffix         => '.c',
      except          => 0,
      hiertype        => 0,
      inout           => 1,
      linenumbers     => 1,
      optimize        => 1,
      output          => \*STDOUT,
      prototypes      => 0,
      typemap         => [],
      versioncheck    => 1,
      in_fh           => Symbol::gensym(),
      die_on_error    => $DIE_ON_ERROR, # if true we die() and not exit()
                                        # after errors
      author_warnings    => $AUTHOR_WARNINGS,
      %opts,
    );
  }

  # Global Constants

  my ($Is_VMS, $VMS_SymSet);

  if ($^O eq 'VMS') {
    $Is_VMS = 1;
    # Establish set of global symbols with max length 28, since xsubpp
    # will later add the 'XS_' prefix.
    require ExtUtils::XSSymSet;
    $VMS_SymSet = ExtUtils::XSSymSet->new(28);
  }

  # XS_parse_stack is an array of hashes. Each hash records the current
  # state when a new file is INCLUDEd, or when within a (possibly nested)
  # file-scoped #if / #ifdef.
  # The 'type' field of each hash is either 'file' for INCLUDE, or 'if'
  # for within an #if / #endif.
  @{ $self->{XS_parse_stack} } = ({type => 'none'});

  $self->{bootcode_early} = [];
  $self->{bootcode_later} = [];

  # hash of package name => package C name
  $self->{map_overloaded_package_to_C_package} = {};
  # hashref of package name => fallback setting
  $self->{map_package_to_fallback_string}     = {};
  $self->{error_count}  = 0; # count

  # 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 %Options.  -Ken

  $self->{config_RetainCplusplusHierarchicalTypes} = $Options{hiertype};
  $self->{PROTOTYPES_value} = $Options{prototypes};
  $self->{VERSIONCHECK_value} = $Options{versioncheck};
  $self->{config_WantLineNumbers} = $Options{linenumbers};
  $self->{IncludedFiles} = {};

  $self->{config_die_on_error} = $Options{die_on_error};
  $self->{config_author_warnings} = $Options{author_warnings};

  die "Missing required parameter 'filename'" unless $Options{filename};


  # allow a string ref to be passed as an in-place filehandle
  if (ref $Options{filename}) {
    my $f = '(input)';
    $self->{in_pathname} = $f;
    $self->{in_filename} = $f;
    $self->{dir}         = '.';
    $self->{IncludedFiles}->{$f}++;
    $Options{outfile}    = '(output)' unless $Options{outfile};
  }
  else {
    ($self->{dir}, $self->{in_filename}) =
        (dirname($Options{filename}), basename($Options{filename}));
    $self->{in_pathname} = $Options{filename};
    $self->{in_pathname} =~ s/\\/\\\\/g;
    $self->{IncludedFiles}->{$Options{filename}}++;
  }

  # 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 $Options{output}) {
    open my($fh), "> $Options{output}" or die "Can't create $Options{output}: $!";
    $Options{outfile} = $Options{output};
    $Options{output} = $fh;
  }

  # Really, we shouldn't have to chdir() or select() in the first
  # place.  For now, just save and restore.
  my $orig_cwd = cwd();
  my $orig_fh = select();

  chdir($self->{dir});
  my $pwd = cwd();

  if ($self->{config_WantLineNumbers}) {
    my $csuffix = $Options{csuffix};
    my $cfile;
    if ( $Options{outfile} ) {
      $cfile = $Options{outfile};
    }
    else {
      $cfile = $Options{filename};
      $cfile =~ s/\.xs$/$csuffix/i or $cfile .= $csuffix;
    }
    tie(*PSEUDO_STDOUT, 'ExtUtils::ParseXS::CountLines', $cfile, $Options{output});
    select PSEUDO_STDOUT;
  }
  else {
    select $Options{output};
  }

  $self->{typemaps_object} = process_typemaps( $Options{typemap}, $pwd );

  $self->{config_strip_c_func_prefix} = $Options{s};
  $self->{config_allow_argtypes}      = $Options{argtypes};
  $self->{config_allow_inout}         = $Options{inout};
  $self->{config_allow_exceptions}    = $Options{except};
  $self->{config_optimize}            = $Options{optimize};

  # Identify the version of xsubpp used
  print <<EOM;
/*
 * This file was generated automatically by ExtUtils::ParseXS version $VERSION from the
 * contents of $self->{in_filename}. Do not edit this file, edit $self->{in_filename} instead.
 *
 *    ANY CHANGES MADE HERE WILL BE LOST!
 *
 */

EOM


  print("#line 1 \"" . escape_file_for_line_directive($self->{in_pathname}) . "\"\n")
    if $self->{config_WantLineNumbers};

  # Open the input file (using $self->{in_filename} which
  # is a basename'd $Options{filename} due to chdir above)
  {
    my $fn   = $self->{in_filename};
    my $opfn = $Options{filename};
    $fn = $opfn if ref $opfn; # allow string ref as a source of file
    open($self->{in_fh}, '<', $fn)
        or die "cannot open $self->{in_filename}: $!\n";
  }

  # ----------------------------------------------------------------
  # Process the first (C language) half of the XS file, up until the first
  # MODULE: line
  # ----------------------------------------------------------------

  FIRSTMODULE:
  while (readline($self->{in_fh})) {
    if (/^=/) {
      my $podstartline = $.;
      do {
        if (/^=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("#if 0\n  \"Skipped embedded POD.\"\n#endif\n");
          printf("#line %d \"%s\"\n", $. + 1, escape_file_for_line_directive($self->{in_pathname}))
            if $self->{config_WantLineNumbers};
          next FIRSTMODULE;
        }

      } while (readline($self->{in_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 $self->{in_filename}, line $podstartline\n")
        unless $self->{lastline};
    }

    last if ($self->{PACKAGE_name}, $self->{PREFIX_pattern}) =
      /^MODULE\s*=\s*[\w:]+(?:\s+PACKAGE\s*=\s*([\w:]+))?(?:\s+PREFIX\s*=\s*(\S+))?\s*$/;

    print $_;
  }

  unless (defined $_) {
    warn "Didn't find a 'MODULE ... PACKAGE ... PREFIX' line\n";
    exit 0; # Not a fatal error for the caller process
  }

  print 'ExtUtils::ParseXS::CountLines'->end_marker, "\n"
    if $self->{config_WantLineNumbers};

  standard_XS_defs();

  print 'ExtUtils::ParseXS::CountLines'->end_marker, "\n"
    if $self->{config_WantLineNumbers};

  $self->{lastline}    = $_;
  $self->{lastline_no} = $.;

  $self->{XS_parse_stack_top_if_idx} = 0;

  my $cpp_next_tmp_define = 'XSubPPtmpAAAA';


  # ----------------------------------------------------------------
  # Main loop: for each iteration, read in a paragraph's worth of XSUB
  # definition or XS/CPP directives into @{ $self->{line} }, then (over
  # the course of a thousand lines of code) try to interpret those lines.
  # ----------------------------------------------------------------

 PARAGRAPH:
  while ($self->fetch_para()) {
    # Process and emit any initial C-preprocessor lines and blank
    # lines.  Also, keep track of #if/#else/#endif nesting, updating:
    #    $self->{XS_parse_stack}
    #    $self->{XS_parse_stack_top_if_idx}
    #    $self->{bootcode_early}
    #    $self->{bootcode_later}

    while (@{ $self->{line} } && $self->{line}->[0] !~ /^[^\#]/) {
      my $ln = shift(@{ $self->{line} });
      print $ln, "\n";
      next unless $ln =~ /^\#\s*((if)(?:n?def)?|elsif|else|endif)\b/;
      my $statement = $+;
      # update global tracking of #if/#else etc
      $self->analyze_preprocessor_statement($statement);
    }

    next PARAGRAPH unless @{ $self->{line} };

    if (   $self->{XS_parse_stack_top_if_idx}
        && !$self->{XS_parse_stack}->[$self->{XS_parse_stack_top_if_idx}]{varname})
    {
      # We are inside an #if, but have not yet #defined its xsubpp variable.
      #
      # At the start of every '#if ...' which is external to an XSUB,
      # we emit '#define XSubPPtmpXXXX 1', for increasing XXXX.
      # Later, when emitting initialisation code in places like a boot
      # block, it can then be made conditional via, e.g.
      #    #if XSubPPtmpXXXX
      #        newXS(...);
      #    #endif
      # So that only the defined XSUBs get added to the symbol table.
      print "#define $cpp_next_tmp_define 1\n\n";
      push(@{ $self->{bootcode_early} }, "#if $cpp_next_tmp_define\n");
      push(@{ $self->{bootcode_later} }, "#if $cpp_next_tmp_define\n");
      $self->{XS_parse_stack}->[$self->{XS_parse_stack_top_if_idx}]{varname}
          = $cpp_next_tmp_define++;
    }

    # This will die on something like
    #
    #   |    CODE:
    #   |        foo();
    #   |
    #   |#define X
    #   |        bar();
    #
    # due to the define starting at column 1 and being preceded by a blank
    # line: so the define and bar() aren't parsed as part of the CODE
    # block.

    $self->death(
      "Code is not inside a function"
        ." (maybe last function was ended by a blank line "
        ." followed by a statement on column one?)")
      if $self->{line}->[0] =~ /^\s/;

    # Initialize some per-XSUB instance variables:

    $self->{xsub_seen_PROTOTYPE}       = 0;
    $self->{xsub_seen_SCOPE}           = 0;
    $self->{xsub_seen_INTERFACE_or_MACRO} = 0;
    $self->{xsub_interface_macro}      = 'XSINTERFACE_FUNC';
    $self->{xsub_interface_macro_set}  = 'XSINTERFACE_FUNC_SET';
    $self->{xsub_prototype}            = $self->{PROTOTYPES_value};
    $self->{xsub_SCOPE_enabled}        = 0;
    $self->{xsub_map_overload_name_to_seen} = {};
    $self->{xsub_seen_NO_OUTPUT}            = 0;
    $self->{xsub_seen_extern_C}             = 0;
    $self->{xsub_seen_static}               = 0;
    $self->{xsub_seen_PPCODE}               = 0;
    $self->{xsub_seen_CODE}                 = 0;
    $self->{xsub_seen_INTERFACE}            = 0;
    $self->{xsub_class}                     = undef;
    $self->{xsub_sig}                       = undef;

    # used for emitting XSRETURN($XSRETURN_count) if > 0, or XSRETURN_EMPTY
    my $XSRETURN_count = 0;


    # Process next line

    $_ = shift(@{ $self->{line} });

    # ----------------------------------------------------------------
    # Process file-scoped keywords
    # ----------------------------------------------------------------

    # Note that MODULE and TYPEMAP will already have been processed by
    # fetch_para().
    #
    # This loop repeatedly: skips any blank lines and then calls
    # $self->FOO_handler() if it finds any of the file-scoped keywords
    # in the passed pattern. $_ is updated and is available to the
    # handlers.
    #
    # Each of the handlers acts on just the current line, apart from the
    # INCLUDE ones, which open a new file and skip any leading blank
    # lines.

    while (my $kwd = $self->check_keyword("REQUIRE|PROTOTYPES|EXPORT_XSUB_SYMBOLS|FALLBACK|VERSIONCHECK|INCLUDE(?:_COMMAND)?|SCOPE")) {
      my $method = $kwd . "_handler";
      $self->$method($_);
      next PARAGRAPH unless @{ $self->{line} };
      $_ = shift(@{ $self->{line} });
    }

    if ($self->check_keyword("BOOT")) {
      $self->BOOT_handler();
      # BOOT: is a file-scoped keyword which consumes all the lines
      # following it in the current paragraph (as opposed to just until
      # the next keyword, like CODE: etc).
      next PARAGRAPH;
    }

    # ----------------------------------------------------------------
    # Process the presumed start of an XSUB
    # ----------------------------------------------------------------

    # Whitespace-tidy the line containing the return type plus possibly
    # the function name and arguments too (The latter was probably an
    # unintended side-effect of later allowing the return type and
    # function to be on the same line.)
    ($self->{xsub_return_type}) = ExtUtils::Typemaps::tidy_type($_);

    $self->{xsub_seen_NO_OUTPUT} = 1
      if $self->{xsub_return_type} =~ s/^NO_OUTPUT\s+//;

    # Allow one-line declarations. This splits a single line like:
    #    int foo(....)
    # into the two lines:
    #    int
    #    foo(...)
    # Note that this splits both K&R-style 'foo(a, b)' and ANSI-style
    # 'foo(int a, int b)'. I don't know whether the former was intentional.
    # As of 5.40.0, the docs don't suggest that a 1-line K&R is legal. Was
    # added by 11416672a16, first appeared in 5.6.0.
    #
    # NB: $self->{config_allow_argtypes} is false if xsubpp was invoked
    # with -noargtypes

    unshift @{ $self->{line} }, $2
      if $self->{config_allow_argtypes}
        and $self->{xsub_return_type} =~ s/^(.*?\w.*?)\s*\b(\w+\s*\(.*)/$1/s;

    # a function definition needs at least 2 lines
    $self->blurt("Error: Function definition too short '$self->{xsub_return_type}'"), next PARAGRAPH
      unless @{ $self->{line} };

    $self->{xsub_seen_extern_C} = 1
                          if $self->{xsub_return_type} =~ s/^extern "C"\s+//;
    $self->{xsub_seen_static}   = 1
                          if $self->{xsub_return_type} =~ s/^static\s+//;

    my ExtUtils::ParseXS::Node::Sig $sig
        = $self->{xsub_sig}
        = ExtUtils::ParseXS::Node::Sig->new();

    {
      my $func_header = shift(@{ $self->{line} });

      # Decompose the function declaration: match a line like
      #   Some::Class::foo_bar(  args  ) const ;
      #   -----------  -------   ----    ----- --
      #       $1        $2        $3      $4   $5
      #
      # where everything except $2 and $3 are optional and the 'const'
      # is for C++ functions.

      $self->blurt("Error: Cannot parse function definition from '$func_header'"), next PARAGRAPH
        unless $func_header =~ /^(?:([\w:]*)::)?(\w+)\s*\(\s*(.*?)\s*\)\s*(const)?\s*(;\s*)?$/s;

      ($self->{xsub_class}, $self->{xsub_func_name}, $sig->{sig_text})
          = ($1, $2, $3);

      $self->{xsub_class} = "$4 $self->{xsub_class}" if $4;

      if ($self->{xsub_seen_static}
          and !defined $self->{xsub_class})
      {
        $self->Warn(  "Ignoring 'static' type modifier:"
                    . " only valid with an XSUB name which includes a class");
        $self->{xsub_seen_static} = 0;
      }

      ($self->{xsub_func_full_perl_name} = $self->{xsub_func_name}) =~
          s/^($self->{PREFIX_pattern})?/$self->{PACKAGE_class}/;

      my $clean_func_name;
      ($clean_func_name = $self->{xsub_func_name}) =~ s/^$self->{PREFIX_pattern}//;
      $self->{xsub_func_full_C_name} = "$self->{PACKAGE_C_name}_$clean_func_name";
      if ($Is_VMS) {
        $self->{xsub_func_full_C_name} = $VMS_SymSet->addsym( $self->{xsub_func_full_C_name} );
      }

      # At this point, supposing that the input so far was:
      #
      #   MODULE = ... PACKAGE = BAR::BAZ PREFIX = foo_
      #   int
      #   Some::Class::foo_bar(  args  ) const ;
      #
      # we should have:
      #
      # $self->{xsub_class}               'const Some::Class'
      # $self->{xsub_func_name}           'foo_bar'
      # $self->{xsub_func_full_perl_name} 'BAR::BAZ::bar'
      # $self->{xsub_func_full_C_name}    'BAR__BAZ_bar';
      #
      # $sig->{sig_text}                  'param1, param2, param3'


      # Check for a duplicate function definition, but ignoring multiple
      # definitions within the branches of an #if/#else/#endif
      for my $tmp (@{ $self->{XS_parse_stack} }) {
        next unless defined $tmp->{functions}{ $self->{xsub_func_full_C_name} };
        Warn( $self, "Warning: duplicate function definition '$clean_func_name' detected");
        last;
      }
    }

    # mark C function name as used
    $self->{XS_parse_stack}->[$self->{XS_parse_stack_top_if_idx}]{functions}{ $self->{xsub_func_full_C_name} }++;

    # initialise more per-XSUB state
    delete $self->{xsub_map_alias_name_to_value};           # ALIAS: ...
    delete $self->{xsub_map_alias_value_to_name_seen_hash};
                                            # INTERFACE: foo bar
    %{ $self->{xsub_map_interface_name_short_to_original} } = ();
    @{ $self->{xsub_attributes} }  = ();    # ATTRS:     lvalue method
    $self->{xsub_SETMAGIC_state} = 1;       # SETMAGIC:  ENABLE

    # ----------------------------------------------------------------
    # Process the XSUB's signature.
    #
    # Split $self->{xsub_sub}{sig_text} into parameters, parse them,
    # and store them as Node::Param objects within the Node::Sig object.

    $sig->parse($self);

    # ----------------------------------------------------------------
    # Peek ahead into the body of the XSUB looking for various conditions
    # that are needed to be known early.
    # ----------------------------------------------------------------

    $self->{xsub_seen_ALIAS}  = grep(/^\s*ALIAS\s*:/,  @{ $self->{line} });

    $self->{xsub_seen_PPCODE}   = !!grep(/^\s*PPCODE\s*:/,    @{$self->{line}});
    $self->{xsub_seen_CODE}     = !!grep(/^\s*CODE\s*:/,      @{$self->{line}});
    $self->{xsub_seen_INTERFACE}= !!grep(/^\s*INTERFACE\s*:/, @{$self->{line}});

    # Horrible 'void' return arg count hack.
    #
    # Until about 1996, xsubpp always emitted 'XSRETURN(1)', even for a
    # void XSUB. This was fixed for CODE-less void XSUBs simply by
    # actually honouring the 'void' type and emitting 'XSRETURN_EMPTY'
    # instead. However, for CODE blocks, the documentation had already
    # endorsed a coding style al