3

私はPerlスクリプトを書いていますが、完全修飾されたJavaクラス名への参照を1行ずつチェックしてJavaソースファイルを解析する必要があります。私は自分が探しているクラスを前もって知っています。また、検索されているソースファイルの完全修飾名(パスに基づく)。

たとえば、com / bob / is/YourUncle.javaファイル内のfoo.bar.Bazへのすべての有効な参照を見つけます。

現時点で、説明する必要があると私が考えることができるケースは次のとおりです。

  1. 解析されるファイルは、検索クラスと同じパッケージに含まれています。

    foo / bar/Boing.javaでfoo.bar.Baz参照を見つけます

  2. コメントは無視する必要があります。

    // this is a comment saying this method returns a foo.bar.Baz or Baz instance 
    // it shouldn't count
    
    /*   a multiline comment as well
     this shouldn't count
     if I put foo.bar.Baz or Baz in here either */
    
  3. インラインの完全修飾参照。

    foo.bar.Baz fb = new foo.bar.Baz();
    
  4. importステートメントに基づく参照。

    import foo.bar.Baz;
    ...
    Baz b = new Baz();
    

Perl 5.8でこれを行う最も効率的な方法は何でしょうか?いくつかの派手な正規表現はおそらく?

open F, $File::Find::name or die;
# these three things are already known
# $classToFind    looking for references of this class
# $pkgToFind      the package of the class you're finding references of
# $currentPkg     package name of the file being parsed
while(<F>){
  # ... do work here   
}
close F;
# the results are availble here in some form
4

5 に答える 5

5

正規表現はおそらくこれに対する最良の解決策ですが、CPAN で使用できる可能性のある次のモジュールを見つけました。

  • Java::JVM::Classfile - コンパイルされたクラス ファイルを解析し、それらに関する情報を返します。これを使用するには、ファイルをコンパイルする必要があります。

また、正規表現を使用して複数行のコメントのすべてのバリエーションをキャッチするのは難しい場合があることを覚えておいてください。

于 2008-09-25T07:15:09.173 に答える
5

引用符で囲まれた文字列もスキップする必要があります (引用符で囲まれた文字列も処理しないと、コメントを正しくスキップすることさえできません)。

おそらく、ノード 566467で書いたものと非常によく似た、かなり単純で効率的で不完全なトークナイザーを書くでしょう。

\bimport\bそのコードに基づいて、コメントや文字列以外のチャンクを掘り下げて一致を探し\b\Q$toFind\E\bます。おそらく次のようになります。

if( m[
        \G
        (?:
            [^'"/]+
          | /(?![/*])
        )+
    ]xgc
) {
    my $code = substr( $_, $-[0], $+[0] - $-[0] );
    my $imported = 0;
    while( $code =~ /\b(import\s+)?\Q$package\E\b/g ) {
        if( $1 ) {
            ... # Found importing of package
            while( $code =~ /\b\Q$class\E\b/g ) {
                ... # Found mention of imported class
            }
            last;
        }
        ... # Found a package reference
    }
} elsif( m[ \G ' (?: [^'\\]+ | \\. )* ' ]xgc
    ||   m[ \G " (?: [^"\\]+ | \\. )* " ]xgc
) {
    # skip quoted strings
} elsif(  m[\G//.*]g­c  ) {
    # skip C++ comments
于 2008-09-25T07:33:14.510 に答える
2

これは私が思いついたものであり、私がそれに投げかけたさまざまなケースすべてに有効です。私はまだPerlの初心者であり、おそらく世界で最速ではありませんが、必要なものには機能するはずです。彼らは私がさまざまな方法でそれを見るのを助けてくれたすべての答えに感謝します。

  my $className = 'Baz';
  my $searchPkg = 'foo.bar';
  my @potentialRefs, my @confirmedRefs;
  my $samePkg = 0;
  my $imported = 0;
  my $currentPkg = 'com.bob';
  $currentPkg =~ s/\//\./g;
  if($currentPkg eq $searchPkg){
    $samePkg = 1;  
  }
  my $inMultiLineComment = 0;
  open F, $_ or die;
  my $lineNum = 0;
  while(<F>){
    $lineNum++;
    if($inMultiLineComment){
      if(m|^.*?\*/|){
        s|^.*?\*/||; #get rid of the closing part of the multiline comment we're in
        $inMultiLineComment = 0;
      }else{
        next;
      }
    }
    if(length($_) > 0){
      s|"([^"\\]*(\\.[^"\\]*)*)"||g; #remove strings first since java cannot have multiline string literals
      s|/\*.*?\*/||g;  #remove any multiline comments that start and end on the same line
      s|//.*$||;  #remove the // comments from what's left
      if (m|/\*.*$|){
        $inMultiLineComment = 1 ;#now if you have any occurence of /* then then at least some of the next line is in the multiline comment
        s|/\*.*$||g;
      }
    }else{
      next; #no sense continuing to process a blank string
    }

    if (/^\s*(import )?($searchPkg)?(.*)?\b$className\b/){
      if($imported || $samePkg){
        push(@confirmedRefs, $lineNum);
      }else {
        push(@potentialRefs, $lineNum);
      }
      if($1){
        $imported = 1;
      } elsif($2){
        push(@confirmedRefs, $lineNum);
      }
    }
  }
  close F;      
  if($imported){
    push(@confirmedRefs,@potentialRefs);
  }

  for (@confirmedRefs){
    print "$_\n";
  }
于 2008-09-26T23:39:04.797 に答える
2

これは、実際には Baz (または、some.other.Baz からの誤検知が心配な場合は /(foo.bar.| )Baz/) に対する単純な grep ですが、コメントは無視されますね。

もしそうなら、状態エンジンをまとめて、あなたが複数行のコメントに入っているかどうかを追跡します。必要な正規表現は特別なものではありません。(テストされていないコード)の行に沿ったもの:

my $in_comment;
my %matches;
my $line_num = 0;
my $full_target = 'foo.bar.Baz';
my $short_target = (split /\./, $full_target)[-1];  # segment after last . (Baz)

while (my $line = <F>) {
    $line_num++;
    if ($in_comment) {
        next unless $line =~ m|\*/|;  # ignore line unless it ends the comment
        $line =~ s|.*\*/||;           # delete everything prior to end of comment
    } elsif ($line =~ m|/\*|) {
        if ($line =~ m|\*/|) {        # catch /* and */ on same line
            $line =~ s|/\*.*\*/||;
        } else {
            $in_comment = 1;
            $line =~ s|/\*.*||;       # clear from start of comment to end of line
        }
    }

    $line =~ s/\\\\.*//;   # remove single-line comments
    $matches{$line_num} = $line if $line =~ /$full_target| $short_target/;
}

for my $key (sort keys %matches) {
    print $key, ': ', $matches{$key}, "\n";
}

これは完璧ではなく、ネストされた複数行のコメントによって、または同じ行に複数の複数行のコメントがある場合、コメントのイン/アウトの状態が台無しになる可能性がありますが、ほとんどの現実のケースではおそらくそれで十分です.

状態エンジンなしでそれを行うには、単一の文字列に丸呑みし、/ ... / コメントを削除して、それを別の行に分割し、//- コメント以外のヒットを grep する必要があります。ただし、そのように出力に行番号を含めることはできません。

于 2008-09-25T07:36:18.263 に答える
1

十分に冒険心がある場合は、Parse::RecDescentをご覧ください。

于 2008-09-29T17:31:45.607 に答える