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 main function in this file is process_file(), which oversees the
# whole job of reading in a .xs file, parsing it into an Abstract Syntax
# Tree (AST), then walking the tree to generate C code and output it to a
# .c file.
#
# Most of the actual logic is in the ExtUtils::ParseXS::Node::FOO
# subclasses, which hold the nodes of the AST. The parse() methods of
# these subclasses do a top-down recursive-descent parse of the input
# file, building the AST; while the as_code() methods walk the tree,
# emitting C code.
#
# The main parsing loop is contained in the Node::cpp_scope::parse()
# method, which in turn relies on fetch_para() to read a paragraph's worth
# of lines from the input while stripping out any POD or XS comments. It
# is fetch_para() which decides where an XSUB, BOOT or TYPEMAP block ends,
# mainly by using a blank line followed by character in column 1 as the
# delimiter (except for TYPEMAP, where it looks for the matching EOF-style
# string).
#
# The remainder of this file mainly consists of helper functions 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.
#
# 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.62';
  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
  set_cond
  Warn
  WarnHint
  current_line_number
  blurt
  death
  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;

# Match an XS Keyword
our $BLOCK_regexp = '\s*(' . $ExtUtils::ParseXS::Constants::XSKeywordsAlternation . ")\\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:

  'AST',                # the Node::XS_file object representing the AST
                        # tree for the whole XS file

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

  'cpp_next_tmp_define',# the next string like XSubPPtmpAAAA
                        # to use as CPP defines for distringuishing
                        # similar calls to newXS() 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
                        # "TRUE", "FALSE", "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

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

  # Most of the parser 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};


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

  my $AST = $self->{AST} = ExtUtils::ParseXS::Node::XS_file->new();
  $AST->parse($self)
    or $self->death("Failed to parse XS file\n");
  $AST->as_code($self);

  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;


# 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 */';
}


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


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


# fetch_para(): private helper method for Node::cpp_scope::parse().
#
# Read in all the lines associated with the next XSUB, BOOT or TYPEMAP,
# or associated with the next contiguous block of file-scoped XS or
# C-preprocessor directives. The caller relies on the paragraph
# demarcation to indicate the end of the XSUB, TYPEMAP or BOOT. For other
# types of line, it doesn't matter how they are split.
#
# More precisely, it reads 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 skips lines which contain POD or XS comments.
#
# It assumes that, on entry, $self->{lastline} contains the next line to
# process, and that further lines can be read from $self->{in_fh} as
# necessary. 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 and fetch_para() returns false.
#
# 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.
#
# 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: ENABLE
#    |#define FOO 1
#    |PHASER DISCOMBOBULARISE
#    |#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.
#
# The following items are handled specially by fetch_para().
#     
# POD:        Discard all lines between /^='/../^=cut/, then continue.
#
# #comment    Discard any line starting with /^\s*#/ which doesn't look
#             like a C preprocessor directive,
#
# TYPEMAP:    Return the typemap 'heredoc' lines as a paragraph, but with
#             the final line (e.g. "EOF") missing. Line continuations,
#             i.e. '\' aren't processed.
#
# BOOT:       BOOT is NOT handled specially; the normal rules for ending
#             a paragraph will determine where the BOOT code ends.
#
# #if etc:    C preprocessor conditional directives are analysed to
#             determine whether they are internal or external to the
#             current paragraph. This allows XSUBs and similar to be
#             closely cuddled by #if/#endif etc without needing to be
#             separated by a blank line. Typically, any such directives
#             immediately preceding an XSUB will be returned as one-line
#             paragraphs.
#
#             Note that this CPP-line analysis is completely independent
#             of a similar analysis done in Node::cpp_scope::parse(),
#             which is concerned with splitting the tree into separate
#             sections where multiple XSUBs with the same name can appear.
#
#             CPP directives (like #define) which aren't concerned with
#             conditions are just passed through without any analysis.
#
# It removes any trailing blank lines from the list of returned lines.


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

  return 0 if not defined $self->{lastline}; # EOF

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

  my $if_level = 0; # current depth of #if/#endif nesting

  # Main loop: for each iteration, process the current line,
  # then maybe read in the next line and continue. Handle some special
  # cases like POD in their own little loop which may read multiple
  # lines.

  for (;;) {

    my $final; # if true, end loop after reading in the next line


    # Skip an embedded POD section

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


    # If present, extract out a TYPEMAP block as a paragraph
    if ($self->{lastline} =~ /^TYPEMAP\s*:/) {

      # Return what we have already and process this line on the
      # next call; that way something like a previous BOOT: won't
      # run on into the TYPEMAP: lines
      last if @{$self->{line}};

      $self->{lastline} =~
          /^TYPEMAP\s*:\s*<<\s*(?:(["'])(.+?)\1|([^\s'"]+?))\s*;?\s*$/
      or $self->death("Error: unparseable TYPEMAP line: '$self->{lastline}'");

      my $end_marker = quotemeta(defined($1) ? $2 : $3);

      # Add the 'TYPEMAP:' line
      push @{$self->{line}},    $self->{lastline};
      push @{$self->{line_no}}, $.;

      # Accumulate lines until we find $end_marker alone on a line.
      while ($self->{lastline} = readline($self->{in_fh})) {
        last if $self->{lastline} =~ /^$end_marker\s*$/;
        chomp $self->{lastline};
        push @{$self->{line}},    $self->{lastline};
        push @{$self->{line_no}}, $.;
      }
      $self->death("Error: Unterminated TYPEMAP section")
        unless defined $self->{lastline};
      $final = 1;
      goto read_next_line;
    }


    # Strip code comment lines

    if ($self->{lastline} =~ /^\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)
        && $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
    ) {
      # A line starting with # but not a CPP directive?
      # Must be a code comment. Skip it.
      goto read_next_line;
    }


    # Blank line followed by char in column 1. Start of next XSUB?

    last if    $self->{lastline} =~ /^\S/
            && @{ $self->{line} }
            && $self->{line}->[-1] eq "";


    # Must be a general line (e.g. file-scoped keyword or CPP directive):
    # process it.

    # Analyse a CPP conditional line and if appropriate, make this line
    # the last line of the current paragraph, or the first line of the
    # next paragraph.

    if ($self->{lastline}
          =~/^#[ \t]*(if|ifn?def|elif|else|endif|elifn?def)\b/)
    {
      # Allow a CPP conditional to directly precede or follow an XSUB
      # without the usual required blank line, e.g.
      #
      #    #if X
      #    void foo()
      #       CODE:
      #       ...
      #    #  if Y
      #       ...
      #    #  endif
      #       ...
      #    #else
      #       ...
      #
      # This is achieved by keeping track of CPP conditional nesting, to
      # determine whether the conditional (e.g. the #else above) is part
      # of the current paragraph, or is paired with something outside it.
      # In this example, the #if Y / #endif are internal to the paragraph,
      # while the #else is external and therefore indicates the end of the
      # current paragraph and so we should stop, even though "\n\n\S"
      # hasn't been encountered.
      #
      # Similarly we stop at the external '#if X', although here it is
      # trickier to distinguish internal from external. For #if's, we
      # achieve this by stopping if the #if is the first line in the
      # putative paragraph; otherwise treat it as internal.

      my $type = $1;

      if (!@{$self->{line}}) {
        # Treat a conditional starting the paragraph as a one-line
        # paragraph
        $final = 1;
      }
      else {
        # Handle conditionals appearing in, or just after, an XSUB

        $if_level++ if $type =~ /^if/; #  if, ifdef, ifndef
        # If we're in a conditional that didn't start in this paragraph,
        # return everything up to, but not including, this line, which
        # will instead form the first line of the *next* paragraph
        return 1 if !$if_level;
        $if_level-- if $type eq "endif";
      }
    }

    push(@{ $self->{line} }, $self->{lastline});
    push(@{ $self->{line_no} }, $self->{lastline_no});


  read_next_line:
    # Read next line and any continuation lines into $self->{lastline_no},
    # ready for the next iteration, or if $final, to be ready for the next
    # call to fetch_para().

    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:
