Blame parts/ppptools.pl

Packit 7d6a7d
################################################################################
Packit 7d6a7d
#
Packit 7d6a7d
#  ppptools.pl -- various utility functions
Packit 7d6a7d
#
Packit 7d6a7d
################################################################################
Packit 7d6a7d
#
Packit 7d6a7d
#  Version 3.x, Copyright (C) 2004-2013, Marcus Holland-Moritz.
Packit 7d6a7d
#  Version 2.x, Copyright (C) 2001, Paul Marquess.
Packit 7d6a7d
#  Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
Packit 7d6a7d
#
Packit 7d6a7d
#  This program is free software; you can redistribute it and/or
Packit 7d6a7d
#  modify it under the same terms as Perl itself.
Packit 7d6a7d
#
Packit 7d6a7d
################################################################################
Packit 7d6a7d
Packit 7d6a7d
sub cat_file
Packit 7d6a7d
{
Packit 7d6a7d
  eval { require File::Spec };
Packit 7d6a7d
  return $@ ? join('/', @_) : File::Spec->catfile(@_);
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub all_files_in_dir
Packit 7d6a7d
{
Packit 7d6a7d
  my $dir = shift;
Packit 7d6a7d
  local *DIR;
Packit 7d6a7d
Packit 7d6a7d
  opendir DIR, $dir or die "cannot open directory $dir: $!\n";
Packit 7d6a7d
  my @files = grep { !-d && !/^\./ } readdir DIR;  # no dirs or hidden files
Packit 7d6a7d
  closedir DIR;
Packit 7d6a7d
Packit 7d6a7d
  return map { cat_file($dir, $_) } sort @files;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub parse_todo
Packit 7d6a7d
{
Packit 7d6a7d
  my $dir = shift || 'parts/todo';
Packit 7d6a7d
  local *TODO;
Packit 7d6a7d
  my %todo;
Packit 7d6a7d
  my $todo;
Packit 7d6a7d
Packit 7d6a7d
  for $todo (all_files_in_dir($dir)) {
Packit 7d6a7d
    open TODO, $todo or die "cannot open $todo: $!\n";
Packit 7d6a7d
    my $perl = <TODO>;
Packit 7d6a7d
    chomp $perl;
Packit 7d6a7d
    while (<TODO>) {
Packit 7d6a7d
      chomp;
Packit 7d6a7d
      s/#.*//;
Packit 7d6a7d
      s/^\s+//; s/\s+$//;
Packit 7d6a7d
      /^\s*$/ and next;
Packit 7d6a7d
      /^\w+$/ or die "invalid identifier: $_\n";
Packit 7d6a7d
      exists $todo{$_} and die "duplicate identifier: $_ ($todo{$_} <=> $perl)\n";
Packit 7d6a7d
      $todo{$_} = $perl;
Packit 7d6a7d
    }
Packit 7d6a7d
    close TODO;
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return \%todo;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub expand_version
Packit 7d6a7d
{
Packit 7d6a7d
  my($op, $ver) = @_;
Packit 7d6a7d
  my($r, $v, $s) = parse_version($ver);
Packit 7d6a7d
  $r == 5 or die "only Perl revision 5 is supported\n";
Packit 7d6a7d
  my $bcdver = sprintf "0x%d%03d%03d", $r, $v, $s;
Packit 7d6a7d
  return "(PERL_BCDVERSION $op $bcdver)";
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub parse_partspec
Packit 7d6a7d
{
Packit 7d6a7d
  my $file = shift;
Packit 7d6a7d
  my $section = 'implementation';
Packit 7d6a7d
  my $vsec = join '|', qw( provides dontwarn implementation
Packit 7d6a7d
                           xsubs xsinit xsmisc xshead xsboot tests );
Packit 7d6a7d
  my(%data, %options);
Packit 7d6a7d
  local *F;
Packit 7d6a7d
Packit 7d6a7d
  open F, $file or die "$file: $!\n";
Packit 7d6a7d
  while (<F>) {
Packit 7d6a7d
    /[ \t]+$/ and warn "$file:$.: warning: trailing whitespace\n";
Packit 7d6a7d
    if ($section eq 'implementation') {
Packit 7d6a7d
      m!//! && !m!(?:=~|s/).*//! && !m!(?:ht|f)tp(?:s)://!
Packit 7d6a7d
          and warn "$file:$.: warning: potential C++ comment\n";
Packit 7d6a7d
    }
Packit 7d6a7d
    /^##/ and next;
Packit 7d6a7d
    if (/^=($vsec)(?:\s+(.*))?/) {
Packit 7d6a7d
      $section = $1;
Packit 7d6a7d
      if (defined $2) {
Packit 7d6a7d
        my $opt = $2;
Packit 7d6a7d
        $options{$section} = eval "{ $opt }";
Packit 7d6a7d
        $@ and die "$file:$.: invalid options ($opt) in section $section: $@\n";
Packit 7d6a7d
      }
Packit 7d6a7d
      next;
Packit 7d6a7d
    }
Packit 7d6a7d
    push @{$data{$section}}, $_;
Packit 7d6a7d
  }
Packit 7d6a7d
  close F;
Packit 7d6a7d
Packit 7d6a7d
  for (keys %data) {
Packit 7d6a7d
    my @v = @{$data{$_}};
Packit 7d6a7d
    shift @v while @v && $v[0]  =~ /^\s*$/;
Packit 7d6a7d
    pop   @v while @v && $v[-1] =~ /^\s*$/;
Packit 7d6a7d
    $data{$_} = join '', @v;
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  unless (exists $data{provides}) {
Packit 7d6a7d
    $data{provides} = ($file =~ /(\w+)\.?$/)[0];
Packit 7d6a7d
  }
Packit 7d6a7d
  $data{provides} = [$data{provides} =~ /(\S+)/g];
Packit 7d6a7d
Packit 7d6a7d
  if (exists $data{dontwarn}) {
Packit 7d6a7d
    $data{dontwarn} = [$data{dontwarn} =~ /(\S+)/g];
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  my @prov;
Packit 7d6a7d
  my %proto;
Packit 7d6a7d
Packit 7d6a7d
  if (exists $data{tests} && (!exists $data{implementation} || $data{implementation} !~ /\S/)) {
Packit 7d6a7d
    $data{implementation} = '';
Packit 7d6a7d
  }
Packit 7d6a7d
  else {
Packit 7d6a7d
    $data{implementation} =~ /\S/ or die "Empty implementation in $file\n";
Packit 7d6a7d
Packit 7d6a7d
    my $p;
Packit 7d6a7d
Packit 7d6a7d
    for $p (@{$data{provides}}) {
Packit 7d6a7d
      if ($p =~ m#^/.*/\w*$#) {
Packit 7d6a7d
        my @tmp = eval "\$data{implementation} =~ ${p}gm";
Packit 7d6a7d
        $@ and die "invalid regex $p in $file\n";
Packit 7d6a7d
        @tmp or warn "no matches for regex $p in $file\n";
Packit 7d6a7d
        push @prov, do { my %h; grep !$h{$_}++, @tmp };
Packit 7d6a7d
      }
Packit 7d6a7d
      elsif ($p eq '__UNDEFINED__') {
Packit 7d6a7d
        my @tmp = $data{implementation} =~ /^\s*__UNDEFINED__[^\r\n\S]+(\w+)/gm;
Packit 7d6a7d
        @tmp or warn "no __UNDEFINED__ macros in $file\n";
Packit 7d6a7d
        push @prov, @tmp;
Packit 7d6a7d
      }
Packit 7d6a7d
      else {
Packit 7d6a7d
        push @prov, $p;
Packit 7d6a7d
      }
Packit 7d6a7d
    }
Packit 7d6a7d
Packit 7d6a7d
    for (@prov) {
Packit 7d6a7d
      if ($data{implementation} !~ /\b\Q$_\E\b/) {
Packit 7d6a7d
        warn "$file claims to provide $_, but doesn't seem to do so\n";
Packit 7d6a7d
        next;
Packit 7d6a7d
      }
Packit 7d6a7d
Packit 7d6a7d
      # scan for prototypes
Packit 7d6a7d
      my($proto) = $data{implementation} =~ /
Packit 7d6a7d
                   ( ^ (?:[\w*]|[^\S\r\n])+
Packit 7d6a7d
                       [\r\n]*?
Packit 7d6a7d
                     ^ \b$_\b \s*
Packit 7d6a7d
                       \( [^{]* \)
Packit 7d6a7d
                   )
Packit 7d6a7d
                       \s* \{
Packit 7d6a7d
                   /xm or next;
Packit 7d6a7d
Packit 7d6a7d
      $proto =~ s/^\s+//;
Packit 7d6a7d
      $proto =~ s/\s+$//;
Packit 7d6a7d
      $proto =~ s/\s+/ /g;
Packit 7d6a7d
Packit 7d6a7d
      exists $proto{$_} and warn "$file: duplicate prototype for $_\n";
Packit 7d6a7d
      $proto{$_} = $proto;
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  for $section (qw( implementation xsubs xsinit xsmisc xshead xsboot )) {
Packit 7d6a7d
    if (exists $data{$section}) {
Packit 7d6a7d
      $data{$section} =~ s/\{\s*version\s*(<|>|==|!=|>=|<=)\s*([\d._]+)\s*\}/expand_version($1, $2)/gei;
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  $data{provides}   = \@prov;
Packit 7d6a7d
  $data{prototypes} = \%proto;
Packit 7d6a7d
  $data{OPTIONS}    = \%options;
Packit 7d6a7d
Packit 7d6a7d
  my %prov     = map { ($_ => 1) } @prov;
Packit 7d6a7d
  my %dontwarn = exists $data{dontwarn} ? map { ($_ => 1) } @{$data{dontwarn}} : ();
Packit 7d6a7d
  my @maybeprov = do { my %h;
Packit 7d6a7d
                       grep {
Packit 7d6a7d
                         my($nop) = /^Perl_(.*)/;
Packit 7d6a7d
                         not exists $prov{$_}                         ||
Packit 7d6a7d
                             exists $dontwarn{$_}                     ||
Packit 7d6a7d
                             /^D_PPP_/                                ||
Packit 7d6a7d
                             (defined $nop && exists $prov{$nop}    ) ||
Packit 7d6a7d
                             (defined $nop && exists $dontwarn{$nop}) ||
Packit 7d6a7d
                             $h{$_}++;
Packit 7d6a7d
                       }
Packit 7d6a7d
                       $data{implementation} =~ /^\s*#\s*define\s+(\w+)/gm };
Packit 7d6a7d
Packit 7d6a7d
  if (@maybeprov) {
Packit 7d6a7d
    warn "$file seems to provide these macros, but doesn't list them:\n  "
Packit 7d6a7d
         . join("\n  ", @maybeprov) . "\n";
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return \%data;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub compare_prototypes
Packit 7d6a7d
{
Packit 7d6a7d
  my($p1, $p2) = @_;
Packit 7d6a7d
  for ($p1, $p2) {
Packit 7d6a7d
    s/^\s+//;
Packit 7d6a7d
    s/\s+$//;
Packit 7d6a7d
    s/\s+/ /g;
Packit 7d6a7d
    s/(\w)\s(\W)/$1$2/g;
Packit 7d6a7d
    s/(\W)\s(\w)/$1$2/g;
Packit 7d6a7d
  }
Packit 7d6a7d
  return $p1 cmp $p2;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub ppcond
Packit 7d6a7d
{
Packit 7d6a7d
  my $s = shift;
Packit 7d6a7d
  my @c;
Packit 7d6a7d
  my $p;
Packit 7d6a7d
Packit 7d6a7d
  for $p (@$s) {
Packit 7d6a7d
    push @c, map "!($_)", @{$p->{pre}};
Packit 7d6a7d
    defined $p->{cur} and push @c, "($p->{cur})";
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  join " && ", @c;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub trim_arg
Packit 7d6a7d
{
Packit 7d6a7d
  my $in = shift;
Packit 7d6a7d
  my $remove = join '|', qw( NN NULLOK VOL );
Packit 7d6a7d
Packit 7d6a7d
  $in eq '...' and return ($in);
Packit 7d6a7d
Packit 7d6a7d
  local $_ = $in;
Packit 7d6a7d
  my $id;
Packit 7d6a7d
Packit 7d6a7d
  s/[*()]/ /g;
Packit 7d6a7d
  s/\[[^\]]*\]/ /g;
Packit 7d6a7d
  s/\b(?:auto|const|extern|inline|register|static|volatile|restrict)\b//g;
Packit 7d6a7d
  s/\b(?:$remove)\b//;
Packit 7d6a7d
  s/^\s*//; s/\s*$//;
Packit 7d6a7d
Packit 7d6a7d
  if( /^\b(?:struct|union|enum)\s+\w+(?:\s+(\w+))?$/ ) {
Packit 7d6a7d
    defined $1 and $id = $1;
Packit 7d6a7d
  }
Packit 7d6a7d
  else {
Packit 7d6a7d
    if( s/\b(?:char|double|float|int|long|short|signed|unsigned|void)\b//g ) {
Packit 7d6a7d
      /^\s*(\w+)\s*$/ and $id = $1;
Packit 7d6a7d
    }
Packit 7d6a7d
    else {
Packit 7d6a7d
      /^\s*\w+\s+(\w+)\s*$/ and $id = $1;
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  $_ = $in;
Packit 7d6a7d
Packit 7d6a7d
  defined $id and s/\b$id\b//;
Packit 7d6a7d
Packit 7d6a7d
  # these don't matter at all
Packit 7d6a7d
  s/\b(?:auto|extern|inline|register|static|volatile|restrict)\b//g;
Packit 7d6a7d
  s/\b(?:$remove)\b//;
Packit 7d6a7d
Packit 7d6a7d
  s/(?=<\*)\s+(?=\*)//g;
Packit 7d6a7d
  s/\s*(\*+)\s*/ $1 /g;
Packit 7d6a7d
  s/^\s*//; s/\s*$//;
Packit 7d6a7d
  s/\s+/ /g;
Packit 7d6a7d
Packit 7d6a7d
  return ($_, $id);
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub parse_embed
Packit 7d6a7d
{
Packit 7d6a7d
  my @files = @_;
Packit 7d6a7d
  my @func;
Packit 7d6a7d
  my @pps;
Packit 7d6a7d
  my $file;
Packit 7d6a7d
  local *FILE;
Packit 7d6a7d
Packit 7d6a7d
  for $file (@files) {
Packit 7d6a7d
    open FILE, $file or die "$file: $!\n";
Packit 7d6a7d
    my($line, $l);
Packit 7d6a7d
Packit 7d6a7d
    while (defined($line = <FILE>)) {
Packit 7d6a7d
      while ($line =~ /\\$/ && defined($l = <FILE>)) {
Packit 7d6a7d
        $line =~ s/\\\s*//;
Packit 7d6a7d
        $line .= $l;
Packit 7d6a7d
      }
Packit 7d6a7d
      next if $line =~ /^\s*:/;
Packit 7d6a7d
      $line =~ s/^\s+|\s+$//gs;
Packit 7d6a7d
      my($dir, $args) = ($line =~ /^\s*#\s*(\w+)(?:\s*(.*?)\s*)?$/);
Packit 7d6a7d
      if (defined $dir and defined $args) {
Packit 7d6a7d
        for ($dir) {
Packit 7d6a7d
          /^ifdef$/   and do { push @pps, { pre => [], cur => "defined($args)"  }         ; last };
Packit 7d6a7d
          /^ifndef$/  and do { push @pps, { pre => [], cur => "!defined($args)" }         ; last };
Packit 7d6a7d
          /^if$/      and do { push @pps, { pre => [], cur => $args             }         ; last };
Packit 7d6a7d
          /^elif$/    and do { push @{$pps[-1]{pre}}, $pps[-1]{cur}; $pps[-1]{cur} = $args; last };
Packit 7d6a7d
          /^else$/    and do { push @{$pps[-1]{pre}}, $pps[-1]{cur}; $pps[-1]{cur} = undef; last };
Packit 7d6a7d
          /^endif$/   and do { pop @pps                                                   ; last };
Packit 7d6a7d
          /^include$/ and last;
Packit 7d6a7d
          /^define$/  and last;
Packit 7d6a7d
          /^undef$/   and last;
Packit 7d6a7d
          warn "unhandled preprocessor directive: $dir\n";
Packit 7d6a7d
        }
Packit 7d6a7d
      }
Packit 7d6a7d
      else {
Packit 7d6a7d
        my @e = split /\s*\|\s*/, $line;
Packit 7d6a7d
        if( @e >= 3 ) {
Packit 7d6a7d
          my($flags, $ret, $name, @args) = @e;
Packit 7d6a7d
          if ($name =~ /^[^\W\d]\w*$/) {
Packit 7d6a7d
            for (@args) {
Packit 7d6a7d
              $_ = [trim_arg($_)];
Packit 7d6a7d
            }
Packit 7d6a7d
            ($ret) = trim_arg($ret);
Packit 7d6a7d
            push @func, {
Packit 7d6a7d
              name  => $name,
Packit 7d6a7d
              flags => { map { $_, 1 } $flags =~ /./g },
Packit 7d6a7d
              ret   => $ret,
Packit 7d6a7d
              args  => \@args,
Packit 7d6a7d
              cond  => ppcond(\@pps),
Packit 7d6a7d
            };
Packit 7d6a7d
          }
Packit 7d6a7d
          elsif ($name =~ /^[^\W\d]\w*-E<gt>[^\W\d]\w*$/) {
Packit 7d6a7d
            # silenty ignore entries of the form
Packit 7d6a7d
            #    PL_parser-E<gt>linestr
Packit 7d6a7d
            # which documents a struct entry rather than a function
Packit 7d6a7d
          }
Packit 7d6a7d
          else {
Packit 7d6a7d
            warn "mysterious name [$name] in $file, line $.\n";
Packit 7d6a7d
          }
Packit 7d6a7d
        }
Packit 7d6a7d
      }
Packit 7d6a7d
    }
Packit 7d6a7d
Packit 7d6a7d
    close FILE;
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return @func;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub make_prototype
Packit 7d6a7d
{
Packit 7d6a7d
  my $f = shift;
Packit 7d6a7d
  my @args = map { "@$_" } @{$f->{args}};
Packit 7d6a7d
  my $proto;
Packit 7d6a7d
  my $pTHX_ = exists $f->{flags}{n} ? "" : "pTHX_ ";
Packit 7d6a7d
  $proto = "$f->{ret} $f->{name}" . "($pTHX_" . join(', ', @args) . ')';
Packit 7d6a7d
  return $proto;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub format_version
Packit 7d6a7d
{
Packit 7d6a7d
  my $ver = shift;
Packit 7d6a7d
Packit 7d6a7d
  $ver =~ s/$/000000/;
Packit 7d6a7d
  my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/;
Packit 7d6a7d
Packit 7d6a7d
  $v = int $v;
Packit 7d6a7d
  $s = int $s;
Packit 7d6a7d
Packit 7d6a7d
  if ($r < 5 || ($r == 5 && $v < 6)) {
Packit 7d6a7d
    if ($s % 10) {
Packit 7d6a7d
      die "invalid version '$ver'\n";
Packit 7d6a7d
    }
Packit 7d6a7d
    $s /= 10;
Packit 7d6a7d
Packit 7d6a7d
    $ver = sprintf "%d.%03d", $r, $v;
Packit 7d6a7d
    $s > 0 and $ver .= sprintf "_%02d", $s;
Packit 7d6a7d
Packit 7d6a7d
    return $ver;
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return sprintf "%d.%d.%d", $r, $v, $s;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub parse_version
Packit 7d6a7d
{
Packit 7d6a7d
  my $ver = shift;
Packit 7d6a7d
Packit 7d6a7d
  if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) {
Packit 7d6a7d
    return ($1, $2, $3);
Packit 7d6a7d
  }
Packit 7d6a7d
  elsif ($ver !~ /^\d+\.[\d_]+$/) {
Packit 7d6a7d
    die "cannot parse version '$ver'\n";
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  $ver =~ s/_//g;
Packit 7d6a7d
  $ver =~ s/$/000000/;
Packit 7d6a7d
Packit 7d6a7d
  my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/;
Packit 7d6a7d
Packit 7d6a7d
  $v = int $v;
Packit 7d6a7d
  $s = int $s;
Packit 7d6a7d
Packit 7d6a7d
  if ($r < 5 || ($r == 5 && $v < 6)) {
Packit 7d6a7d
    if ($s % 10) {
Packit 7d6a7d
      die "cannot parse version '$ver'\n";
Packit 7d6a7d
    }
Packit 7d6a7d
    $s /= 10;
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return ($r, $v, $s);
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
1;