The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Copyright 2009, 2010 Kevin Ryde

# This file is part of Chart.
#
# Chart is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3, or (at your option) any later version.
#
# Chart is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with Chart.  If not, see <http://www.gnu.org/licenses/>.

package Regexp::Common::Perl;
use strict;
use warnings;
use Carp;
use Regexp::Common ('no_defaults', # no $RE import
                    'pattern',     # pattern func
                   );

# uncomment this to run the ### lines
#use Smart::Comments;

pattern (name   => ['Perl','version'],
         create => 'v?[0-9][0-9.]*');

pattern (name   => ['Perl','identifier'],
         create => '[[:alpha:]_][[:alnum:]_]*');

pattern (name   => ['Perl','qualified'],
         create => sub {"$RE{Perl}{identifier}(?:::$RE{Perl}{identifier})*"});

pattern (name   => ['Perl','lws'],
         create => sub { "(?:\\s+($RE{comment}{Perl}\\s*)*"
                           . "|$RE{comment}{Perl}(\\s*$RE{comment}{Perl})*)" });
pattern (name   => ['Perl','pod'],
         create => "(?:^|\n)(?:^|\n)(=[[:alpha:]][^\n]*\n.*?\n)?=cut(\n|\$)(\n|\$)");

# ENHANCE-ME: also q{} etc strings
pattern (name   => ['Perl','string'],
         create => sub { $RE{delimited}{-delim=>'"\''}{-esc=>'\\\\'} });


package MyExtractUse;
use strict;
use warnings;
use Perl6::Slurp;
use Pod::Strip;
use Regexp::Common;

use constant DEBUG => 0;

print "RE lws $RE{Perl}{lws}\n";
my $lws = qr/$RE{Perl}{lws}/o;
my $use_re
  = qr{\b(?<type>use|require|no)
       $lws
       (?<package>$RE{Perl}{qualified})
       ($lws(?<version>$RE{Perl}{version}))?
    }ox;
my $use_code_re
  = qr{(?<eval>eval${lws}?\{)?
       ${lws}?
       $use_re}ox;
my $use_eval_string_re
  = qr{(?<eval>eval${lws}?['"]
       ${lws}?
       $use_re)}ox;
my $use_base_re
  = qr{\b(?<type>use)${lws}base
       $lws
       (\($lws)?['"](?<package>$RE{Perl}{qualified})
    }ox;
my $use_perl_re
  = qr{\b(?<type>use|no|require)
       ${lws}
       (?<version>$RE{Perl}{version})
      }ox;
my $VERSION_check_re
  = qr{\b(?<package>$RE{Perl}{qualified})
      $lws
      ->
      $lws
      VERSION
      $lws
      \(
      $lws
      ['"]?(?<version>$RE{Perl}{version})
      }ox;

# my $re = $use_eval_string_re;
# print "re: $re\n";
# my $str = "eval 'use Foo::Bar 3; 1' ";
# print "str: $str\n";
# if ($str =~ $use_eval_string_re) {
#   print "match\n";
#   require Data::Dumper;
#   print Data::Dumper::Dumper(\%+);
# } else {
#   print "no match\n";
# }
# exit 0;


sub from_file {
  my ($class, $filename) = @_;
  ### from_file(): $filename
  return if ($filename =~ m{selfloader-fork\.pl$});
  return $class->from_string (scalar Perl6::Slurp::slurp($filename));
}
sub from_string {
  my ($class, $str) = @_;

  my @ret;
  my $one = sub {
    my ($re) = @_;
    if (DEBUG >= 2) { print $str; }

    while ($str =~ /$re/g) {
      my %ret = %+;
      $ret{'pos'} = pos($str);
      $ret{'version'} = version->new($ret{'version'} || 0);
      push @ret, \%ret;

      if (DEBUG) {
        require Data::Dumper;
        print Data::Dumper::Dumper(\%ret);
      }
    }
  };

  #  $str = _pod_to_comments ($str);
  $str = _pod_to_whitespace ($str);
  $str = _comments_to_whitespace ($str);
  $str = _heredoc_to_whitespace ($str);

  $one->($use_base_re);
  $one->($use_eval_string_re);
  $one->($VERSION_check_re);
  $str = _strings_to_whitespace ($str);
  $one->($use_code_re);

  return @ret;
}

my $heredoc_re = qr/<<(?<open>['"]|)(?<word>$RE{Perl}{identifier})\k<open>(.*\n)+?\k<word>/;
  #$str =~ s/($heredoc_re)/_to_whitespace($1)/ego;

sub _heredoc_to_whitespace {
  my ($str) = @_;
  ### _heredoc_to_whitespace()
  while ($str =~ /<<['"]?($RE{Perl}{identifier})/) {
    my $pos = $-[0];
    my $word = $1;
    my $end = index ($str, "\n$word", $pos);
    if ($end < 0) { $end = length($str); }
    substr ($str, $pos, $end-$pos, '');
  }
  ### return: length($str)
  return $str;
}
# print _heredoc_to_whitespace('
#   show <<"HERE";
# foo ""
# HERE
# bar
#   xxx <<EOF;
# EOF
# ');
# exit 0;

sub _comments_to_whitespace {
  my ($str) = @_;
  $str =~ s/($RE{comment}{Perl})/_to_whitespace($1)/ego;
  return $str;
}

sub _pod_to_comments {
  my ($str) = @_;
  ### _pod_to_comments()
  my $stripper = Pod::Strip->new;
  $stripper->replace_with_comments (1);
  my $out;
  $stripper->output_string (\$out);
  $stripper->parse_string_document ($str);
  return $out;
}

sub _pod_to_whitespace {
  my ($str) = @_;
  ### _pod_to_whitespace()
  $str =~ s/($RE{Perl}{pod})/_to_whitespace($1)/ego;
  return $str;
}
# print  _pod_to_whitespace(<<HERE);
# 
# =foo
# 
# =cut
# 
# HERE
# exit 0;

# my $string_single_re = qr/'([^\\']|\\.)*?'/s;
# my $string_double_re = qr/"([^\\"]|\\.)*?"/s;
# #my $string_re = qr/$string_single_re|$string_double_re/o;
#   #$str =~ s/($string_re)/_string_empty($1)/ego;
# my $string_re = qr/(?<open>['"])(?<content>([^\\]|\\.)*?)(?<close>\k<open>)/s;
#   $str =~ s/($string_re)/$+{open}._to_whitespace($+{content}).$+{close}/ego;

sub _strings_to_whitespace {
  my ($str) = @_;
  $str =~ s{($RE{Perl}{string})}
           {_delimited_to_whitespace($1)}ego;
  return $str;
}
#  print qr//,"\n";
#  print _strings_to_whitespace(" 'f\\'oo' 'bar' ");


sub _delimited_to_whitespace {
  my ($str) = @_;
  return substr($str,0,1)
    . _to_whitespace(substr($str,1,-1))
      . substr($str,-1);
};
sub _to_whitespace {
  my ($str) = @_;
  $str =~ s/([^[:space:]]+)/' ' x length($1)/ge;
  return $str;
}

1;
__END__