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.60';
  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(
  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)
our $END = "!End!\n\n";
# Match an XS Keyword
our $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_an_XSUB',       # Bool: at least one XSUB has been encountered

  # File-scoped code-emitting state:

  'need_boot_cv',       # must declare 'cv' within the boot function

  '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:

  'file_SCOPE_enabled',        # Bool: the current state of the file-scope
                               # (as opposed to
                               # XSUB-scope) SCOPE keyword
  );

  # 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

  our ($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;
    $ExtUtils::ParseXS::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 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/;

    # The SCOPE keyword can appear both in file scope (just before an
    # XSUB) and as an XSUB keyword. This field maintains the state of the
    # former: reset it at the start of processing any file-scoped
    # keywords just before the XSUB (i.e. without any blank lines, e.g.
    #     SCOPE: ENABLE
    #     int
    #     foo(...)
    # These semantics may not be particularly sensible, but they maintain
    # backwards compatibility for now.

    $self->{file_SCOPE_enabled} = 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 $class = "ExtUtils::ParseXS::Node::$kwd";
      if ($class->can('parse')) {
        # this branch handles the newer AST-oriented keyword processing
        my $node  = $class->new();
        unshift @{$self->{line}}, $_;
        $node->parse($self);
        $node->as_code($self) if $class->can('as_code');
      }
      else {
        # this branch handles the older KEYWORD_handler()-oriented processing
        my $method = $kwd . "_handler";
        $self->$method($_); # $_ contains the rest of the line after KEYWORD:
      }

      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;
    }

    # ----------------------------------------------------------------
    # Parse and code-emit an XSUB
    # ----------------------------------------------------------------

    unshift @{$self->{line}}, $_;
    my $xsub = ExtUtils::ParseXS::Node::xsub->new();
    $xsub->parse($self)
      or next PARAGRAPH;
    $_ = shift @{$self->{line}};

    $xsub->as_code($self);
    $self->{seen_an_XSUB} = 1; # encountered at least one XSUB

    # ----------------------------------------------------------------
    # end of XSUB
    # ----------------------------------------------------------------

  } # END 'PARAGRAPH' 'while' loop


  # ----------------------------------------------------------------
  # End of main loop and at EOF: all paragraphs (and thus XSUBs) have now
  # been read in and processed.  Do any final post-processing.
  # ----------------------------------------------------------------

  # Process any overloading.
  #
  # For each package FOO which has had at least one overloaded method
  # specified:
  #   - create a stub XSUB in that package called nil;
  #   - generate code to be added to the boot XSUB which links that XSUB
  #     to the symbol table entry *{"FOO::()"}.  This mimics the action in
  #     overload::import() which creates the stub method as a quick way to
  #     check whether an object is overloaded (including via inheritance),
  #     by doing $self->can('()').
  #   - Further down, we add a ${"FOO:()"} scalar containing the value of
  #     'fallback' (or undef if not specified).
  #
  # XXX In 5.18.0, this arrangement was changed in overload.pm, but hasn't
  # been updated here. The *() glob was being used for two different
  # purposes: a sub to do a quick check of overloadability, and a scalar
  # to indicate what 'fallback' value was specified (even if it wasn't
  # specified). The commits:
  #   v5.16.0-87-g50853fa94f
  #   v5.16.0-190-g3866ea3be5
  #   v5.17.1-219-g79c9643d87
  # changed this so that overloadability is checked by &((, while fallback
  # is checked by $() (and not present unless specified by 'fallback'
  # as opposed to the always being present, but sometimes undef).
  # Except that, in the presence of fallback, &() is added too for
  # backcompat reasons (which I don't fully understand - DAPM).
  # See overload.pm's import() and OVERLOAD() methods for more detail.
  #
  # So this code needs updating to match.

  for my $package (sort keys %{ $self->{map_overloaded_package_to_C_package} })
  {
    # make them findable with fetchmethod
    my $packid = $self->{map_overloaded_package_to_C_package}->{$package};
    print Q(<<"EOF");
      |XS_EUPXS(XS_${packid}_nil); /* prototype to pass -Wmissing-prototypes */
      |XS_EUPXS(XS_${packid}_nil)
      |{
      |   dXSARGS;
      |   PERL_UNUSED_VAR(items);
      |   XSRETURN_EMPTY;
      |}
      |
EOF

    unshift(@{ $self->{bootcode_early} }, Q(<<"EOF"));
      |   /* Making a sub named "${package}::()" allows the package */
      |   /* to be findable via fetchmethod(), and causes */
      |   /* overload::Overloaded("$package") to return true. */
      |   (void)newXS_deffile("${package}::()", XS_${packid}_nil);
EOF
  }


  # ----------------------------------------------------------------
  # Emit the boot XSUB initialization routine
  # ----------------------------------------------------------------

  print Q(<<"EOF");
    |#ifdef __cplusplus
    |extern "C" [[
    |#endif
EOF

  print Q(<<"EOF");
    |XS_EXTERNAL(boot_$self->{MODULE_cname}); /* prototype to pass -Wmissing-prototypes */
    |XS_EXTERNAL(boot_$self->{MODULE_cname})
    |[[
    |#if PERL_VERSION_LE(5, 21, 5)
    |    dVAR; dXSARGS;
    |#else
    |    dVAR; ${\($self->{VERSIONCHECK_value} ? 'dXSBOOTARGSXSAPIVERCHK;' : 'dXSBOOTARGSAPIVERCHK;')}
    |#endif
EOF

  # Declare a 'file' var for passing to newXS() and variants.
  #
  # If there is no $self->{seen_an_XSUB} then there are no xsubs
  # in this .xs so 'file' is unused, so silence warnings.
  #
  # 'file' can also be unused in other circumstances: in particular,
  # newXS_deffile() doesn't take a file parameter. So suppress any
  # 'unused var' warning always.
  #
  # Give it the correct 'const'ness: Under 5.8.x and lower, newXS() is
  # declared in proto.h as expecting a non-const file name argument. If
  # the wrong qualifier is used, it causes breakage with C++ compilers and
  # warnings with recent gcc.

  print Q(<<"EOF") if $self->{seen_an_XSUB};
    |#if PERL_VERSION_LE(5, 8, 999) /* PERL_VERSION_LT is 5.33+ */
    |    char* file = __FILE__;
    |#else
    |    const char* file = __FILE__;
    |#endif
    |
    |    PERL_UNUSED_VAR(file);
EOF

  # Emit assorted declarations

  print Q(<<"EOF");
    |
    |    PERL_UNUSED_VAR(cv); /* -W */
    |    PERL_UNUSED_VAR(items); /* -W */
EOF

  if ($self->{VERSIONCHECK_value}) {
    print Q(<<"EOF") ;
    |#if PERL_VERSION_LE(5, 21, 5)
    |    XS_VERSION_BOOTCHECK;
    |#  ifdef XS_APIVERSION_BOOTCHECK
    |    XS_APIVERSION_BOOTCHECK;
    |#  endif
    |#endif
    |
EOF

  } else {
    print Q(<<"EOF") ;
      |#if PERL_VERSION_LE(5, 21, 5) && defined(XS_APIVERSION_BOOTCHECK)
      |  XS_APIVERSION_BOOTCHECK;
      |#endif
      |
EOF

  }

  # Declare a 'cv' var within a scope small enough to be visible just to
  # newXS() calls which need to do further processing of the cv: in
  # particular, when emitting one of:
  #      XSANY.any_i32 = $value;
  #      XSINTERFACE_FUNC_SET(cv, $value);

  if ($self->{need_boot_cv}) {
    print Q(<<"EOF");
      |    [[
      |        CV * cv;
      |
EOF
  }

  # More overload stuff

  if (keys %{ $self->{map_overloaded_package_to_C_package} }) {
    # Emit just once if any overloads:
    # Before 5.10, PL_amagic_generation used to need setting to at least a
    # non-zero value to tell perl that any overloading was present.
    print Q(<<"EOF");
      |    /* register the overloading (type 'A') magic */
      |#if PERL_VERSION_LE(5, 8, 999) /* PERL_VERSION_LT is 5.33+ */
      |    PL_amagic_generation++;
      |#endif
EOF

    for my $package (sort keys %{ $self->{map_overloaded_package_to_C_package} }) {
      # Emit once for each package with overloads:
      # Set ${'Foo::()'} to the fallback value for each overloaded
      # package 'Foo' (or undef if not specified).
      # But see the 'XXX' comments above about fallback and $().
      my $fallback =     $self->{map_package_to_fallback_string}->{$package}
                     || "&PL_sv_undef";
      print Q(<<"EOF");
        |    /* 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

    }
  }

  # Emit any boot code associated with newXS().

  print @{ $self->{bootcode_early} };

  # Emit closing scope for the 'CV *cv' declaration

  if ($self->{need_boot_cv}) {
    print Q(<<"EOF");
      |    ]]
EOF
  }

  # Emit any lines derived from BOOT: sections

  if (@{ $self->{bootcode_later} }) {
    print "\n    /* Initialisation Section */\n\n";
    print @{$self->{bootcode_later}};
    print 'ExtUtils::ParseXS::CountLines'->end_marker, "\n"
      if $self->{config_WantLineNumbers};
    print "\n    /* End of Initialisation Section */\n\n";
  }

  # Emit code to call any UNITCHECK blocks and return true. Since 5.22,
  # this is been put into a separate function.
  print Q(<<'EOF');
    |#if PERL_VERSION_LE(5, 21, 5)
    |#  if PERL_VERSION_GE(5, 9, 0)
    |    if (PL_unitcheckav)
    |        call_list(PL_scopestack_ix, PL_unitcheckav);
    |#  endif
    |    XSRETURN_YES;
    |#else
    |    Perl_xs_boot_epilog(aTHX_ ax);
    |#endif
    |]]
    |
    |#ifdef __cplusplus
    |]]
    |#endif
EOF

  warn("Please specify prototyping behavior for $self->{in_filename} (see perlxs manual)\n")
    unless $self->{proto_behaviour_specified};

  chdir($orig_cwd);
  select($orig_fh);
  untie *PSEUDO_STDOUT if tied *PSEUDO_STDOUT;
  close $self->{in_fh};

  return 1;
}


sub report_error_count {
  if (@_) {
    return $_[0]->{error_count}||0;
  }
  else {
    return $Singleton->{error_count}||0;
  }
}
*errors = \&report_error_count;


# $self->check_keyword("FOO|BAR")
#
# Return a keyword if the next non-blank line matches one of the passed
# keywords, or return undef otherwise.
#
# Expects $_ to be set to the current line. Skip any initial blank lines,
# (consuming @{$self->{line}} and updating $_).
#
# Then if it matches FOO: etc, strip the keyword and any comment from the
# line (leaving any argument in $_) and return the keyword. Return false
# otherwise.

sub check_keyword {
  my ExtUtils::ParseXS $self = shift;
  # skip blank lines
  $_ = shift(@{ $self->{line} }) while !/\S/ && @{ $self->{line} };

  s/^(\s*)($_[0])\s*:\s*(?:#.*)?/$1/s && $2;
}


# Handle BOOT: keyword.
# Save all the remaining lines in the paragraph to the bootcode_later
# array, and prepend a '#line' if necessary.

sub BOOT_handler {
  my ExtUtils::ParseXS $self = shift;

  # Check all the @{ $self->{line}} lines for balance: all the
  # #if, #else, #endif etc within the BOOT should balance out.
  $self->check_conditional_preprocessor_statements();

  # prepend a '#line' directive if needed
  if (   $self->{config_WantLineNumbers}
      && $self->{line}->[0] !~ /^\s*#\s*line\b/)
  {
    push @{ $self->{bootcode_later} },
       sprintf "#line %d \"%s\"\n",
         $self->{line_no}->[@{ $self->{line_no} } - @{ $self->{line} }],
         escape_file_for_line_directive($self->{in_pathname});
  }

  # Save all the BOOT lines plus trailing empty line to be emitted later.
  push @{ $self->{bootcode_later} }, "$_\n" for @{ $self->{line} }, "";
}


# ST(): helper function for the various INPUT / OUTPUT code emitting
# parts.  Generate an "ST(n)" string. This is normally just:
#
#   "ST(". $num - 1 . ")"
#
# except that in input processing it is legal to have a parameter with a
# typemap override, but where the parameter isn't in the signature. People
# misuse this to declare other variables which should really be in a
# PREINIT section:
#
#    int
#    foo(a)
#       int a
#       int b = 0
#
# The '= 0' will be interpreted as a local typemap entry, so $arg etc
# will be populated and the "typemap" evalled, So $num is undef, but we
# shouldn't emit a warning when generating "ST(N-1)".
#
sub ST {
  my ($self, $num) = @_;
  return "ST(" . ($num-1) . ")" if defined $num;
  return '/* not a parameter */';
}


sub FALLBACK_handler {
  my ExtUtils::ParseXS $self = shift;
  my ($setting) = @_;

  # the rest of the current line should contain either TRUE,
  # FALSE or UNDEF

  trim_whitespace($setting);
  $setting = uc($setting);

  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
  $self->death("Error: FALLBACK: TRUE/FALSE/UNDEF") unless exists $map{$setting};

  $self->{map_package_to_fallback_string}->{$self->{PACKAGE_name}}
      = $map{$setting};
}


sub REQUIRE_handler {
  my ExtUtils::ParseXS $self = shift;
  # the rest of the current line should contain a version number
  my ($ver) = @_;

  trim_whitespace($ver);

  $self->death("Error: REQUIRE expects a version number")
    unless $ver;

  # check that the version number is of the form n.n
  $self->death("Error: REQUIRE: expected a number, got '$ver'")
    unless $ver =~ /^\d+(\.\d*)?/;

  $self->death("Error: xsubpp $ver (or better) required--this is only $VERSION.")
    unless $VERSION >= $ver;
}


# Push an entry on the @{ $self->{XS_parse_stack} } array containing the
# current file state, in preparation for INCLUDEing a new file. (Note that
# it doesn't handle type => 'if' style entries, only file entries.)

sub push_parse_stack {
  my ExtUtils::ParseXS $self = shift;
  my %args = @_;
  # Save the current file context.
  push(@{ $self->{XS_parse_stack} }, {
          type            => 'file',
          LastLine        => $self->{lastline},
          LastLineNo      => $self->{lastline_no},
          Line            => $self->{line},
          LineNo          => $self->{line_no},
          Filename        => $self->{in_filename},
          Filepathname    => $self->{in_pathname},
          Handle          => $self->{in_fh},
          IsPipe          => scalar($self->{in_filename} =~ /\|\s*$/),
          %args,
         });

}


sub INCLUDE_handler {
  my ExtUtils::ParseXS $self = shift;
  $_ = shift;
  # the rest of the current line should contain a valid filename

  trim_whitespace($_);

  $self->death("INCLUDE: filename missing")
    unless $_;

  $self->death("INCLUDE: output pipe is illegal")
    if /^\s*\|/;

  # simple minded recursion detector
  $self->death("INCLUDE loop detected")
    if $self->{IncludedFiles}->{$_};

  ++$self->{IncludedFiles}->{$_} unless /\|\s*$/;

  if (/\|\s*$/ && /^\s*perl\s/) {
    Warn( $self, "The INCLUDE directive with a command is discouraged." .
          " Use INCLUDE_COMMAND instead! In particular using 'perl'" .
          " in an 'INCLUDE: ... |' directive is not guaranteed to pick" .
          " up the correct perl. The INCLUDE_COMMAND directive allows" .
          " the use of \$^X as the currently running perl, see" .
          " 'perldoc perlxs' for details.");
  }

  $self->push_parse_stack();

  $self->{in_fh} = Symbol::gensym();

  # open the new file
  open($self->{in_fh}, $_) or $self->death("Cannot open '$_': $!");

  print Q(<<"EOF");
    |
    |/* INCLUDE:  Including '$_' from '$self->{in_filename}' */
    |
EOF

  $self->{in_filename} = $_;
  $self->{in_pathname} = ( $^O =~ /^mswin/i )
                            # See CPAN RT #61908: gcc doesn't like
                            # backslashes on win32?
                          ? qq($self->{dir}/$self->{in_filename})
                          : File::Spec->catfile($self->{dir}, $self->{in_filename});

  # Prime the pump by reading the first
  # non-blank line

  # skip leading blank lines
  while (readline($self->{in_fh})) {
    last unless /^\s*$/;
  }

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


# Quote a command-line to be suitable for VMS

sub QuoteArgs {
  my $cmd = shift;
  my @args = split /\s+/, $cmd;
  $cmd = shift @args;
  for (@args) {
    $_ = q(").$_.q(") if !/^\"/ && length($_) > 0;
  }
  return join (' ', ($cmd, @args));
}


# _safe_quote(): quote an executable pathname which includes spaces.
#
# This code was copied from CPAN::HandleConfig::safe_quote:
# that has doc saying leave if start/finish with same quote, but no code
# given text, will conditionally quote it to protect from shell

{
  my ($quote, $use_quote) = $^O eq 'MSWin32'
      ? (q{"}, q{"})
      : (q{"'}, q{'});
  sub _safe_quote {
      my ($self, $command) = @_;
      # Set up quote/default quote
      if (defined($command)
          and $command =~ /\s/
          and $command !~ /[$quote]/) {
          return qq{$use_quote$command$use_quote}
      }
      return $command;
  }
}


sub INCLUDE_COMMAND_handler {
  my ExtUtils::ParseXS $self = shift;
  $_ = shift;
  # the rest of the current line should contain a valid command

  trim_whitespace($_);

  $_ = QuoteArgs($_) if $^O eq 'VMS';

  $self->death("INCLUDE_COMMAND: command missing")
    unless $_;

  $self->death("INCLUDE_COMMAND: pipes are illegal")
    if /^\s*\|/ or /\|\s*$/;

  $self->push_parse_stack( IsPipe => 1 );

  $self->{in_fh} = Symbol::gensym();

  # If $^X is used in INCLUDE_COMMAND, we know it's supposed to be
  # the same perl interpreter as we're currently running
  my $X = $self->_safe_quote($^X); # quotes if has spaces
  s/^\s*\$\^X/$X/;

  # open the new file
  open ($self->{in_fh}, "-|", $_)
    or $self->death( $self, "Cannot run command '$_' to include its output: $!");

  print Q(<<"EOF");
    |
    |/* INCLUDE_COMMAND:  Including output of '$_' from '$self->{in_filename}' */
    |
EOF

  $self->{in_filename} = $_;
  $self->{in_pathname} = $self->{in_filename};
  #$self->{in_pathname} =~ s/\"/\\"/g; # Fails? See CPAN RT #53938: MinGW Broken after 2.21
  $self->{in_pathname} =~ s/\\/\\\\/g; # Works according to reporter of #53938

  # Prime the pump by reading the first
  # non-blank line

  # skip leading blank lines
  while (readline($self->{in_fh})) {
    last unless /^\s*$/;
  }

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


# Pop the type => 'file' entry off the top of the @{ $self->{XS_parse_stack} }
# array following the end of processing an INCLUDEd file, and restore the
# former state.

sub PopFile {
  my ExtUtils::ParseXS $self = shift;

  return 0 unless $self->{XS_parse_stack}->[-1]{type} eq 'file';

  my $data     = pop @{ $self->{XS_parse_stack} };
  my $ThisFile = $self->{in_filename};
  my $isPipe   = $data->{IsPipe};

  --$self->{IncludedFiles}->{$self->{in_filename}}
    unless $isPipe;

  close $self->{in_fh};

  $self->{in_fh}         = $data->{Handle};
  # $in_filename is the leafname, which for some reason is used for diagnostic
  # messages, whereas $in_pathname is the full pathname, and is used for
  # #line directives.
  $self->{in_filename}   = $data->{Filename};
  $self->{in_pathname} = $data->{Filepathname};
  $self->{lastline}   = $data->{LastLine};
  $self->{lastline_no} = $data->{LastLineNo};
  @{ $self->{line} }       = @{ $data->{Line} };
  @{ $self->{line_no} }    = @{ $data->{LineNo} };

  if ($isPipe and $? ) {
    --$self->{lastline_no};
    print STDERR "Error reading from pipe '$ThisFile': $! in $self->{in_filename}, line $self->{lastline_no}\n" ;
    exit 1;
  }

  print Q(<<"EOF");
    |
    |/* INCLUDE: Returning to '$self->{in_filename}' from '$ThisFile' */
    |
EOF

  return 1;
}


# Unescape a string (typically a heredoc):
#   - strip leading '    |' (any number of leading spaces)
#   - and replace [[ and ]]
#         with    {  and }
# so that text editors don't see a bare { or } when bouncing around doing
# brace level matching.

sub Q {
  my ($text) = @_;
  my @lines = split /^/, $text;
  my $first;
  for (@lines) {
    unless (s/^(\s*)\|//) {
      die "Internal error: no leading '|' in Q() string:\n$_\n";
    }
    my $pre = $1;
    die "Internal error: leading tab char in Q() string:\n$_\n"
      if $pre =~ /\t/;

    if (defined $first) {
      die "Internal error: leading indents in Q() string don't match:\n$_\n"
        if $pre ne $first;
    }
    else {
      $first = $pre;
    }
  }
  $text = join "", @lines;

  $text =~ s/\[\[/{/g;
  $text =~ s/\]\]/}/g;
  $text;
}


# Process "MODULE = Foo ..." lines and update global state accordingly

sub _process_module_xs_line {
  my ExtUtils::ParseXS $self = shift;
  my ($module, $pkg, $prefix) = @_;

  ($self->{MODULE_cname} = $module) =~ s/\W/_/g;

  $self->{PACKAGE_name} = defined($pkg) ? $pkg : '';
  $self->{PREFIX_pattern} = quotemeta( defined($prefix) ? $prefix : '' );

  ($self->{PACKAGE_C_name} = $self->{PACKAGE_name}) =~ tr/:/_/;

  $self->{PACKAGE_class} = $self->{PACKAGE_name};
  $self->{PACKAGE_class} .= "::" if $self->{PACKAGE_class} ne "";

  $self->{lastline} = "";
}


# Skip any embedded POD sections, reading in lines from {in_fh} as necessary.

sub _maybe_skip_pod {
  my ExtUtils::ParseXS $self = shift;

  while ($self->{lastline} =~ /^=/) {
    while ($self->{lastline} = readline($self->{in_fh})) {
      last if ($self->{lastline} =~ /^=cut\s*$/);
    }
    $self->death("Error: Unterminated pod") unless defined $self->{lastline};
    $self->{lastline} = readline($self->{in_fh});
    chomp $self->{lastline};
    $self->{lastline} =~ s/^\s+$//;
  }
}


# Strip out and parse embedded TYPEMAP blocks (which use a HEREdoc-alike
# block syntax).

sub _maybe_parse_typemap_block {
  my ExtUtils::ParseXS $self = shift;

  # This is special cased from the usual paragraph-handler logic
  # due to the HEREdoc-ish syntax.
  if ($self->{lastline} =~ /^TYPEMAP\s*:\s*<<\s*(?:(["'])(.+?)\1|([^\s'"]+?))\s*;?\s*$/)
  {
    my $end_marker = quotemeta(defined($1) ? $2 : $3);

    # Scan until we find $end_marker alone on a line.
    my @tmaplines;
    while (1) {
      $self->{lastline} = readline($self->{in_fh});
      $self->death("Error: Unterminated TYPEMAP section") if not defined $self->{lastline};
      last if $self->{lastline} =~ /^$end_marker\s*$/;
      push @tmaplines, $self->{lastline};
    }

    my $tmap = ExtUtils::Typemaps->new(
      string        => join("", @tmaplines),
      lineno_offset => 1 + ($self->current_line_number() || 0),
      fake_filename => $self->{in_filename},
    );
    $self->{typemaps_object}->merge(typemap => $tmap, replace => 1);

    $self->{lastline} = "";
  }
}


# fetch_para(): private helper method for process_file().
#
# Read in all the lines associated with the next XSUB, or associated with
# the next contiguous block of file-scoped XS or CPP directives.
#
# More precisely, read lines (and their line numbers) up to (but not
# including) the start of the next XSUB or similar, into:
#
#   @{ $self->{line}    }
#   @{ $self->{line_no} }
#
# It assumes that $self->{lastline} contains the next line to process,
# and that further lines can be read from $self->{in_fh} as necessary.
#
# Multiple lines which are read in that end in '\' are concatenated
# together into a single line, whose line number is set to
# their first line. The two characters '\' and '\n' are kept in the
# concatenated string.
#
# On return, it leaves the first unprocessed line in $self->{lastline}:
# typically the first line of the next XSUB. At EOF, lastline will be
# left undef.
#
# In general, it stops just before the first line which matches /^\S/ and
# which was preceded by a blank line. This line is often the start of the
# next XSUB (but there is no guarantee of that).
#
# For example, given these lines:
#
#    |    ....
#    |    stuff
#    |                    [blank line]
#    |PROTOTYPES: ENABLED
#    |#define FOO 1
#    |SCOPE: ENABLE
#    |#define BAR 1
#    |                    [blank line]
#    |int
#    |foo(...)
#    |    ....
#
# then the first call will return everything up to 'stuff' inclusive
# (perhaps it's the last line of an XSUB). The next call will return four
# lines containing the XS directives and CPP definitions. The directives
# are not interpreted or processed by this function; they're just returned
# as unprocessed text for the caller to interpret. A third call will read
# in the XSUB starting at 'int'.
#
# Note that fetch_para() knows almost nothing about C or XS syntax and
# keywords, and just blindly reads in lines until it finds a suitable
# place to break. It generally relies on the caller to handle most of the
# syntax and semantics and error reporting. For example, the block of four
# lines above from 'PROTOTYPES' onwards isn't valid XS, but is blindly
# returned by fetch_para().
#
# It often returns zero lines - the caller will have to handle this.
#
# There are a few exceptions where certain lines starting in column 1
# *are* interpreted by this function (and conversely where /\\$/ *isn't*
# processed):
#     
# POD:        Discard all lines between /^='/../^=cut/, then continue.
#
# MODULE:     If this appears as the first line, it is processed and
#             discarded, then line reading continues.
#
# TYPEMAP:    Process a 'heredoc' typemap, discard all processed lines,
#             then continue.
#
# /^\s*#/     Discard such lines unless they look like a CPP directive,
#             on the assumption that they are code comments. Then, in
#             particular:
#
# #if etc:    For anything which is part of a CPP conditional: if it
#             is external to the current chunk of code (e.g. an #endif
#             which isn't matched by an earlier #if/ifdef/ifndef within
#             the current chunk) then processing stops before that line.
#
#             Nested if/elsif/else's etc within the chunk are passed
#             through and processing continues. An #if/ifdef/ifdef on the
#             first line is treated as external and is returned as a
#             single line.
#
#             It is assumed the caller will handle any processing or
#             nesting of external conditionals.
#
#             CPP directives (like #define) which aren't concerned with
#             conditions are just passed through.
#
# It removes any trailing blank lines from the list of returned lines.


sub fetch_para {
  my ExtUtils::ParseXS $self = shift;

  # unmatched #if at EOF
  $self->death("Error: Unterminated '#if/#ifdef/#ifndef'")
    if !defined $self->{lastline} && $self->{XS_parse_stack}->[-1]{type} eq 'if';

  @{ $self->{line} } = ();
  @{ $self->{line_no} } = ();
  return $self->PopFile() if not defined $self->{lastline}; # EOF

  if ($self->{lastline} =~
      /^MODULE\s*=\s*([\w:]+)(?:\s+PACKAGE\s*=\s*([\w:]+))?(?:\s+PREFIX\s*=\s*(\S+))?\s*$/)
  {
    $self->_process_module_xs_line($1, $2, $3);
  }

  # count how many #ifdef levels we see in this paragraph
  # decrementing when we see an endif. if we see an elsif
  # or endif without a corresponding #ifdef then we don't
  # consider it part of this paragraph.
  my $if_level = 0;

  for (;;) {
    $self->_maybe_skip_pod;

    $self->_maybe_parse_typemap_block;

    my $final;

    # Process this line unless it looks like a '#', comment

    if ($self->{lastline} !~ /^\s*#/ # not a CPP directive
           # 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)
        || $self->{lastline} =~ /^\#[ \t]*
                                  (?:
                                        (?:if|ifn?def|elif|else|endif|elifn?def|
                                           define|undef|pragma|error|
                                           warning|line\s+\d+|ident)
                                        \b
                                      | (?:include(?:_next)?|import)
                                        \s* ["<] .* [>"]
                                 )
                                /x
    )
    {
      # Blank line followed by char in column 1. Start of next XSUB?
      last if    $self->{lastline} =~ /^\S/
              && @{ $self->{line} }
              && $self->{line}->[-1] eq "";

      # processes CPP conditionals
      if ($self->{lastline}
            =~/^#[ \t]*(if|ifn?def|elif|else|endif|elifn?def)\b/)
      {
        my $type = $1;
        if ($type =~ /^if/) {  # if, ifdef, ifndef
          if (@{$self->{line}}) {
            # increment level
            $if_level++;
          } else {
            $final = 1;
          }
        } elsif ($type eq "endif") {
          if ($if_level) { # are we in an if that was started in this paragraph?
            $if_level--;   # yep- so decrement to end this if block
          } else {
            $final = 1;
          }
        } elsif (!$if_level) {
          # not in an #ifdef from this paragraph, thus
          # this directive should not be part of this paragraph.
          $final = 1;
        }
      }

      if ($final and @{$self->{line}}) {
        return 1;
      }

      push(@{ $self->{line} }, $self->{lastline});
      push(@{ $self->{line_no} }, $self->{lastline_no});
    } # end of processing non-comment lines

    # Read next line and continuation lines
    last unless defined($self->{lastline} = readline($self->{in_fh}));
    $self->{lastline_no} = $.;
    my $tmp_line;
    $self->{lastline} .= $tmp_line
      while ($self->{lastline} =~ /\\$/ && defined($tmp_line = readline($self->{in_fh})));

    chomp $self->{lastline};
    $self->{lastline} =~ s/^\s+$//;
    if ($final) {
      last;
    }
  } # end for (;;)

  # Nuke trailing "line" entries until there's one that's not empty
  pop(@{ $self->{line} }), pop(@{ $self->{line_no} })
    while @{ $self->{line} } && $self->{line}->[-1] eq "";

  return 1;
}


# These two subs just delegate to a method in a clean package, where there
# are as few lexical variables in scope as possible and the ones which are
# accessible (such as $arg) are the ones documented to be available when
# eval()ing (in double-quoted context) the initialiser on an INPUT or
# OUTPUT line such as 'int foo = SvIV($arg)'

sub eval_output_typemap_code {
  my ExtUtils::ParseXS $self = shift;
  my ($code, $other) = @_;
  return ExtUtils::ParseXS::Eval::eval_output_typemap_code($self, $code, $other);
}

sub eval_input_typemap_code {
  my ExtUtils::ParseXS $self = shift;
  my ($code, $other) = @_;
  return ExtUtils::ParseXS::Eval::eval_input_typemap_code($self, $code, $other);
}

1;

# vim: ts=2 sw=2 et:
