Правило ANTLR для пропуска тела метода

Моя задача — создать грамматику ANTLR, проанализировать файлы исходного кода C# и сгенерировать иерархию классов. Затем я буду использовать его для создания диаграммы классов.

Я написал правила для разбора пространств имен, объявлений классов и объявлений методов. Теперь у меня проблема с пропуском тел методов. Мне не нужно их разбирать, потому что тела бесполезны в моей задаче.

Я написал простое правило:

body:
'{' .* '}'
;

но он не работает должным образом, когда метод выглядит так:

void foo()
{
  ...
  {
    ...
  }
  ...
}

правило соответствует первой фигурной скобке, что в порядке, затем соответствует

... 
{
  ...

как «любой» (.*), а затем третья скобка в качестве последней скобки, что не в порядке, и правило заканчивается.

Кто-нибудь может помочь мне написать правильное правило для тел методов? Как я уже говорил, я не хочу их разбирать - только пропускать.

ОБНОВЛЕНИЕ:

вот решение моей проблемы, основанное на ответе Adam12

body:
'{' ( ~('{' | '}') | body)* '}'
;

person GrzegorzM    schedule 09.09.2012    source источник
comment
Вам предстоит очень сложная задача. Вам придется принимать пары { и } внутри тела, а также игнорировать комментарии и строковое содержимое внутри тела. строки типа [{ очень распространены при создании небольших частей json или /* if (...) { something*/ для некоторого кода, который временно удален, и они будут нарушать ваши правила.   -  person Casperah    schedule 09.09.2012
comment
@Casperah, не могли бы вы привести пример, как принимать пары {}? Я думаю, что здесь следует использовать рекурсию, но не более того   -  person GrzegorzM    schedule 09.09.2012


Ответы (2)


Вы должны использовать рекурсивные правила, которые соответствуют парам скобок.

rule1 : '(' 
  (
    nestedParan
  | (~')')*
  )
  ')';

nestedParan : '('
  (
    nestedParan
  | (~')')*
  )
  ')';

Этот код предполагает, что вы используете синтаксический анализатор, поэтому строки и комментарии уже исключены. ANTLR не допускает отрицания нескольких альтернатив в правилах синтаксического анализатора, поэтому приведенный выше код основан на том факте, что альтернативы проверяются по порядку. Он должен выдать предупреждение о том, что альтернативы 1 и 2 соответствуют '(', и, таким образом, выбрать первую альтернативу, что нам и нужно.

person Community    schedule 10.09.2012

Вы можете обрабатывать рекурсию (вложенных) блоков в свой лексер. Хитрость заключается в том, чтобы ваше определение класса также включало открытие {, чтобы не все содержимое класса было поглощено этим правилом рекурсивного лексера.

Быстрая демонстрация, которая, без сомнения, не завершена, но является достойным началом "нечеткого разбора/лексики" исходного файла Java (или C# с некоторыми небольшими изменениями):

grammar T;

parse
 : (t=. {System.out.printf("\%-15s '\%s'\n", tokenNames[$t.type], $t.text.replace("\n", "\\n"));})* EOF
 ;

Skip
 : (StringLiteral | CharLiteral | Comment) {skip();}
 ;

PackageDecl
 : 'package' Spaces Ids {setText($Ids.text);}
 ;

ClassDecl
 : 'class' Spaces Id Spaces? '{' {setText($Id.text);}
 ;

Method
 : Id Spaces? ('(' {setText($Id.text);}
              | /* no method after all! */ {skip();}
              )
 ;

MethodOrStaticBlock
 : Block {skip();}
 ;

Any
 : . {skip();}
 ;

// fragments
fragment Spaces 
 : (' ' | '\t' | '\r' | '\n')+
 ;

fragment Ids
 : Id ('.' Id)*
 ;

fragment Id 
 : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*
 ;

fragment Block
 : '{' ( ~('{' | '}' | '"' | '\'' | '/')
       | {input.LA(2) != '/'}?=> '/'
       | StringLiteral
       | CharLiteral
       | Comment
       | Block
       )*
   '}'
 ;

fragment Comment
 : '/*' .* '*/'
 | '//' ~('\r' | '\n')*
 ;

fragment CharLiteral
 : '\'' ('\\\'' | ~('\\' | '\'' | '\r' | '\n'))+ '\''
 ;

fragment StringLiteral
 : '"' ('\\"' | ~('\\' | '"' | '\r' | '\n'))* '"'
 ;

Я запустил сгенерированный парсер для следующего исходного файла Java:

/*
    ... package NO.PACKAGE; ...
*/
package foo.bar;

public final class Mu {

  static String x;

  static {
    x = "class NotAClass!";
  }

  void m1() {
    // {
    while(true) {
      double a = 2.0 / 2;
      if(a == 1.0) { break; } // }
      /* } */
    }
  }

  static class Inner {
    int m2   () {return 42; /*comment}*/ }
  }
}

который произвел следующий вывод:

PackageDecl     'foo.bar'
ClassDecl       'Mu'
Method          'm1'
ClassDecl       'Inner'
Method          'm2'
person Bart Kiers    schedule 10.09.2012