ANTLR: как анализировать регион в соответствующих скобках с помощью лексера

я хочу разобрать что-то вроде этого в моем лексере:

( begin expression )

где выражения также заключены в квадратные скобки. не важно, что находится в выражении, я просто хочу иметь все, что между (begin и совпадающим ) в качестве токена. пример:

(begin 
    (define x (+ 1 2)))

поэтому текст токена должен быть

(define x (+ 1 2)))

что-то типа

PROGRAM : LPAREN BEGIN .* RPAREN;

(очевидно) не работает, потому что, как только он видит ")", он думает, что правило закончилось, но для этого мне нужна соответствующая скобка.

Как я могу это сделать?


person zhujik    schedule 02.08.2011    source источник
comment
Если вы хотите распознавать все, что находится между (...), включая другие вложенные (...), то вы не можете сделать это с помощью чистого стандартного лексера на основе регулярных выражений (например, как у ANTLR, я думаю), потому что регулярные выражения могут ' t count - ›не может определить, когда скобки уравновешены. Вам необходимо реализовать распознаватели лексем, которые могут считать круглые скобки.   -  person Ira Baxter    schedule 03.08.2011


Ответы (1)


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

Демо:

T.g

grammar T;

parse
  :  BeginToken {System.out.println("parsed :: " + $BeginToken.text);} EOF
  ;

BeginToken 
@init{int open = 1;}
  :  '(' 'begin' ( {open > 0}?=>              // keep reapeating `( ... )*` as long as open > 0
                     ( ~('(' | ')')           // match anything other than parenthesis
                     | '('          {open++;} // match a '(' in increase the var `open`
                     | ')'          {open--;} // match a ')' in decrease the var `open`
                     )
                 )*
  ;

Main.java

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String input = "(begin (define x (+ (- 1 3) 2)))";
    TLexer lexer = new TLexer(new ANTLRStringStream(input));
    TParser parser = new TParser(new CommonTokenStream(lexer));
    parser.parse();
  }
}
java -cp antlr-3.3-complete.jar org.antlr.Tool T.g
javac -cp antlr-3.3-complete.jar *.java
java -cp .:antlr-3.3-complete.jar Main

parsed :: (begin (define x (+ (- 1 3) 2)))

Обратите внимание, что вам нужно остерегаться строковых литералов внутри вашего источника, которые могут включать круглые скобки:

BeginToken
@init{int open = 1;}
  :  '(' 'begin' ( {open > 0}?=>              // ...
                     ( ~('(' | ')' | '"')     // ...
                     | '('          {open++;} // ...
                     | ')'          {open--;} // ...
                     |  '"' ...               // TODO: define a string literal here
                     )
                 )*
  ;

или комментарии, которые могут содержать скобки.

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

BeginToken 
  :  '(' Spaces? 'begin' Spaces? NestedParens Spaces? ')'
  ;

fragment NestedParens
  :  '(' ( ~('(' | ')') | NestedParens )* ')'
  ;

fragment Spaces
  :  (' ' | '\t')+
  ;
person Bart Kiers    schedule 03.08.2011
comment
@Ira, это отдельная лексема. Правила, начинающиеся с верхнего регистра, являются токенами в ANTLR. Мое название правила Root, возможно, было неудачным выбором, что наводит вас на мысль, что я переложил проблему на синтаксический анализатор ... - person Bart Kiers; 03.08.2011
comment
Ага. Да, как вы могли догадаться, я не совсем специалист по ANTLR. Итак ... это ответ, который, как я ожидал, вы дадите. Я никогда больше в тебе не сомневаюсь! - person Ira Baxter; 03.08.2011
comment
@Bart: я только что заметил еще одну проблему: лексер больше не распознает утверждения типа (bar ...) правильно, вместо этого он отбрасывает '(ba' и просто возвращает r в качестве идентификатора, хотя я хочу иметь LPAREN IDENTIFIER. как я могу это исправить? я должен открыть для этого еще один вопрос и снова опубликовать грамматику? - person zhujik; 05.09.2011
comment
Да, лексеру это не нравится. В случае ввода, который выглядит как (b..., у лексера проблемы с резервным копированием в LPAREN IDENTIFIER вместо BeginToken. Вы действительно хотите обработать это в своем лексере? Было бы легче сопоставить beginToken в анализаторе, чем BeginToken в лексере. И да, вероятно, было бы проще создать новый вопрос (если вы действительно хотите сделать это в лексере). - person Bart Kiers; 06.09.2011