Как я могу определить ссылки на классы Java с помощью Perl? - PullRequest
3 голосов
/ 25 сентября 2008

Я пишу сценарий Perl, и я пришел к тому, что мне нужно проанализировать строку исходного файла Java, проверяя строки на наличие полного имени класса Java. Я знаю класс, который я ищу заранее; также полное имя искомого исходного файла (на основе его пути).

Например, найдите все допустимые ссылки на foo.bar.Baz в файле com / bob / is / YourUncle.java.

На данный момент я могу подумать, что нужно учесть следующие случаи:

  1. Анализируемый файл находится в том же пакете, что и класс поиска.

    найти ссылки на foo.bar.Baz в foo / bar / Boing.java

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

Ответы [ 5 ]

5 голосов
/ 25 сентября 2008

Вам также нужно пропускать строки в кавычках (вы даже не можете правильно пропустить комментарии, если вы также не имеете дело со строками в кавычках).

Я бы, вероятно, написал довольно простой, эффективный и неполный токенизатор, очень похожий на тот, который я написал в узле 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
5 голосов
/ 25 сентября 2008

Regex, вероятно, является лучшим решением для этого, хотя я нашел следующий модуль в CPAN, который вы могли бы использовать

  • Java :: JVM :: Classfile - Анализирует скомпилированные файлы классов и возвращает информацию о них. Вам придется скомпилировать файлы, прежде чем вы сможете использовать это.

Кроме того, помните, что может быть сложно поймать все возможные варианты многострочного комментария с помощью регулярного выражения.

2 голосов
/ 27 сентября 2008

Это то, что я придумал, это работает для всех разных случаев, которые я в него бросил. Я все еще Perl Noob, и это, вероятно, не самая быстрая вещь в мире, но она должна работать для того, что мне нужно Спасибо за все ответы, они помогли мне взглянуть на это по-разному.

  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";
  }
2 голосов
/ 25 сентября 2008

На самом деле это просто прямое выражение для Baz (или для / (foo.bar. |) Baz /, если вы беспокоитесь о ложных срабатываниях от some.other.Baz), но игнорируете комментарии, не так ли?

Если это так, я бы собрал движок состояний, чтобы отследить, есть ли у вас многострочный комментарий или нет. Необходимые регулярные выражения не являются чем-то особенным. Что-то вроде ( непроверенный код ):

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

Это не идеально, и состояние входа / выхода из комментария может быть испорчено вложенными многострочными комментариями или если в одной строке есть несколько многострочных комментариев, но, вероятно, этого достаточно для большинства реальных случаев.

Чтобы обойтись без движка состояний, вам нужно добавить в одну строку, удалить комментарии /.../ и разделить их на отдельные строки, а затем добавить их для - // - комментарий хитов. Но вы не сможете включить номера строк в вывод таким образом.

1 голос
/ 29 сентября 2008

Если вы чувствуете себя достаточно предприимчивым, вы можете взглянуть на Parse :: RecDescent .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...