Я играю с ANTLR, чтобы написать парсер для структуры объектов PDF, но столкнулся с проблемой парсинга строкового потока, смешанного с PDF Reference и Integer.

По сути, ссылка в формате PDF представляет собой строку, подобную этой: «10 0 R» (INTEGER SPACE INTEGER SPACE ‘R’).

Вот мой файл грамматики (упрощенный):

grammar Pdf;

options {
language=CSharp3;
backtrack=true;
}

public r returns [string val]
    :   ref {$val = $r.text;}
    |   INTEGER {$val = $r.text;}
    ;

ref
    :   INTEGER SPACE INTEGER SPACE 'R';

INTEGER
    :   DIGIT+;

SPACE: ' ';

fragment DIGIT
    :   '0'..'9'
    ;

Вот тестовый код (на С#):

byte[] bytes = Encoding.ASCII.GetBytes("97 98 10 0 R 100 101");
MemoryStream stream = new MemoryStream(bytes);

ANTLRInputStream inputStream = new ANTLRInputStream(stream);
PdfLexer lexer = new PdfLexer(inputStream);
CommonTokenStream tokens = new CommonTokenStream(lexer);

PdfParser parser = new PdfParser(tokens);
string result = parser.r();

Я ожидаю, что результатом будет первое правило, соответствующее правилу r (будь то ref или INTEGER).

Например:

  • если input="97 98 10 0 R 100 101": результат="97"

  • если input="10 0 R 100 101": результат="10 0 R"

Нет необходимости проходить через весь поток строк. Просто сопоставьте первое правило, затем остановитесь.

Я новичок в ANTLR и не мог понять, как это сделать. Я использую ANTLRWorks 1.4.3 и antlr-dotnet-csharpruntime-3.4.1.9004.

Любая помощь приветствуется!

0
neolei 28 Дек 2011 в 15:34

1 ответ

backtrack=true применяется только к правилам парсера, а не к правилам лексера. Поэтому, когда лексер натыкается на INTEGER SPACE, за которым следует что-то отличное от INTEGER, лексер выдает ошибку/исключение: он не отмените правило REF и вместо этого создайте токен INTEGER и SPACE.

Но REF не должно быть правилом лексера для начала, а правилом парсера:

ref
 : INTEGER SPACE INTEGER SPACE 'R'
 ;

Редактировать

Я работаю в Linux и поэтому не могу протестировать цель C# (по крайней мере, мне никогда не удавалось запустить цель CSharp3 внутри MonoDevelop). Но вот демонстрация Java:

grammar Pdf;

public r
 : ( ref     {System.out.println("ref     = '" + $ref.text + "'");}
   | INTEGER {System.out.println("INTEGER = '" + $INTEGER.text + "'");}
   | SPACE   {System.out.println("SPACE   = '" + $SPACE.text + "'");}
   )*
   EOF
 ;

ref
 : INTEGER SPACE INTEGER SPACE 'R'
 ;

INTEGER
 : DIGIT+;

SPACE
 : ' '
 ;

fragment DIGIT
 : '0'..'9'
 ;

Вы можете протестировать парсер с помощью класса:

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    PdfLexer lexer = new PdfLexer(new ANTLRStringStream("97 98 10 0 R 100 101"));
    PdfParser parser = new PdfParser(new CommonTokenStream(lexer));
    parser.r();
  }
}

И если вы запустите этот класс, будет напечатано следующее:

INTEGER = '97'
SPACE   = ' '
INTEGER = '98'
SPACE   = ' '
ref     = '10 0 R'
SPACE   = ' '
INTEGER = '100'
SPACE   = ' '
INTEGER = '101'

Что именно так, как я ожидал.

1
Ragunath Jawahar 8 Июл 2013 в 15:03
Я изменил REF на ref, но это не сработало. Хотя там что-то другое, теперь жетоны стали: 97, ПРОБЕЛ, 98, ПРОБЕЛ, 10, ПРОБЕЛ, 0, ПРОБЕЛ, 'R', ПРОБЕЛ, 101, EOF. Он распознает только INTEGER, а не ref.
 – 
neolei
29 Дек 2011 в 14:58
@ sun1991, когда я проверяю, все работает отлично. См. мой EDIT.
 – 
Bart Kiers
29 Дек 2011 в 15:15
@Kiers, я могу продублировать результат на своей машине при выводе на C#, что для меня большой шаг, спасибо! Извините, я не ясно дал понять в своем вопросе, но мое первоначальное намерение - не использовать правило r:(...)* для прохождения всего потока строк. Я бы хотел, чтобы r() возвращал только первое правило, которому оно соответствует, будь то ref или INTEGER. Если ввод «10 0 R 100 101», он должен вернуть «ref = 10 0 R» и остановиться, если ввод «97 98 10 0 R», он просто вернет «INTEGER = 97» и остановится. Я пытаюсь удалить скобки и * вокруг правила r, но снова ничего не возвращает.
 – 
neolei
29 Дек 2011 в 18:14