如何使用Perl识别对Java类的引用?
我正在编写一个Perl脚本,现在到了需要逐行分析Java源文件来检查对标准Java类名称的引用的地步。我知道我正在寻找的班级。也是正在搜索的源文件的标准名称(基于其路径)。
例如,在com / bob / is / YourUncle.java文件中找到对foo.bar.Baz的所有有效引用。
目前,我能想到的情况是:
- 所解析的文件与搜索类位于同一程序包中。在foo / bar / Boing.java中找到foo.bar.Baz引用
- 它应该忽略评论。
// 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 */
- 在线完全合格的参考。
foo.bar.Baz fb = new foo.bar.Baz();
- 基于导入语句的引用。
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
解决方案
尽管我确实在CPAN中找到了我们可能可以使用的以下模块,但是Regex可能是对此的最佳解决方案。
- Java :: JVM :: Classfile-解析编译的类文件并返回有关它们的信息。我们必须先编译文件,然后才能使用此文件。
另外,请记住,使用正则表达式捕获多行注释的所有可能变体可能很棘手。
我们还需要跳过带引号的字符串(如果我们还不处理带引号的字符串,则甚至无法正确跳过注释)。
我可能会编写一个非常简单,高效且不完整的令牌生成器,与在节点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( ) { ... # 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
对于Baz(或者/(foo.bar。|)Baz /,如果我们担心来自some.other.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。但是我们将无法以这种方式在输出中包含行号。
这就是我想出的方法,适用于我抛出的所有不同情况。我仍然是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(){ $imported = 1; } elsif(){ push(@confirmedRefs, $lineNum); } } } close F; if($imported){ push(@confirmedRefs,@potentialRefs); } for (@confirmedRefs){ print "$_\n"; }
如果我们有足够的冒险精神,可以看看Parse :: RecDescent。