ANTLR грамматика для reStructuredText (правила приоритетов) - PullRequest
4 голосов
/ 30 мая 2011

Первый поток вопросов

Привет всем,

Это может быть продолжение этого вопроса: Приоритеты правил Antlr

Я пытаюсь написать грамматику ANTLR для языка разметки reStructuredText .

Основная проблема, с которой я сталкиваюсь: «Как сопоставить любую последовательность символов (обычный текст) без маскировки других правил грамматики?»

Давайте рассмотрим пример с абзацем со встроенной разметкой:

In `Figure 17-6`_, we have positioned ``before_ptr`` so that it points to the element 
*before* the insert point. The variable ``after_ptr`` points to the element *after* the 
insert. In other words, we are going to put our new element **in between** ``before_ptr`` 
and ``after_ptr``.

Я думал, что написание правил для встроенного текста разметки будет легко. Поэтому я написал простую грамматику:

grammar Rst;

options {
    output=AST;
    language=Java;
    backtrack=true;
    //memoize=true;
}

@members {
boolean inInlineMarkup = false;
}

// PARSER

text
    : inline_markup (WS? inline_markup)* WS? EOF
    ;


inline_markup
@after {
inInlineMarkup = false;
}
    : {!inInlineMarkup}? (emphasis|strong|litteral|link)
    ;

emphasis
@init {
inInlineMarkup = true;
}
    : '*' (~'*')+ '*' {System.out.println("emphasis: " + $text);}
    ;

strong
@init {
inInlineMarkup = true;
}
    : '**' (~'*')+ '**' {System.out.println("bold: " + $text);}
    ;

litteral
@init {
inInlineMarkup = true;
}
    : '``' (~'`')+ '``' {System.out.println("litteral: " + $text);}
    ;

link
@init {
inInlineMarkup = true;
}
    : inline_internal_target
    | footnote_reference
    | hyperlink_reference
    ;

inline_internal_target
    : '_`' (~'`')+ '`' {System.out.println("inline_internal_target: " + $text);}
    ;

footnote_reference
    : '[' (~']')+ ']_' {System.out.println("footnote_reference: " + $text);}
    ;


hyperlink_reference
    : ~(' '|'\t'|'\u000C'|'_')+ '_' {System.out.println("hyperlink_reference: " + $text);}
    |   '`' (~'`')+ '`_' {System.out.println("hyperlink_reference (long): " + $text);}
    ;

// LEXER

WS  
  : (' '|'\t'|'\u000C')+
  ; 

NEWLINE
  : '\r'? '\n'
  ;

Эта простая грамматика не работает. И я даже не пытался сопоставить обычный текст ...

Мои вопросы:

  • Может ли кто-нибудь указать на мои ошибки и, возможно, дать мне подсказку о том, как сопоставить обычный текст?
  • Есть ли способ установить приоритет для правил грамматики? Может быть, это может быть лидерство.

Заранее спасибо за помощь: -)

Robin


Второй поток вопросов

Большое спасибо за вашу помощь! Мне было бы трудно понять свои ошибки ... Я не пишу эту грамматику (только) для изучения ANTLR, я пытаюсь написать код для плагина IDE для Eclipse. И для этого мне нужна грамматика;)

Мне удалось пойти дальше в грамматике и написать text правило:

grammar Rst;

options {
    output=AST;
    language=Java;
}



@members {
boolean inInlineMarkup = false;
}

//////////////////
// PARSER RULES //
//////////////////

file
  : line* EOF
  ;


line
  : text* NEWLINE
  ;

text
    : inline_markup
    | normal_text
    ;

inline_markup
@after {
inInlineMarkup = false;
}
    : {!inInlineMarkup}? {inInlineMarkup = true;} 
  (
  | STRONG
  | EMPHASIS
  | LITTERAL
  | INTERPRETED_TEXT
  | SUBSTITUTION_REFERENCE
  | link
  )
    ;


link
    : INLINE_INTERNAL_TARGET
    | FOOTNOTE_REFERENCE
    | HYPERLINK_REFERENCE
    ;

normal_text
  : {!inInlineMarkup}? 
   ~(EMPHASIS
      |SUBSTITUTION_REFERENCE
      |STRONG
      |LITTERAL
      |INTERPRETED_TEXT
      |INLINE_INTERNAL_TARGET
      |FOOTNOTE_REFERENCE
      |HYPERLINK_REFERENCE
      |NEWLINE
      )
  ;
//////////////////
// LEXER TOKENS //
//////////////////

EMPHASIS
    : STAR ANY_BUT_STAR+ STAR {System.out.println("EMPHASIS: " + $text);}
    ;

SUBSTITUTION_REFERENCE
  : PIPE ANY_BUT_PIPE+ PIPE  {System.out.println("SUBST_REF: " + $text);}
  ;

STRONG
    : STAR STAR ANY_BUT_STAR+ STAR STAR {System.out.println("STRONG: " + $text);}
    ;

LITTERAL
    : BACKTICK BACKTICK ANY_BUT_BACKTICK+ BACKTICK BACKTICK {System.out.println("LITTERAL: " + $text);}
    ;
INTERPRETED_TEXT
  : BACKTICK ANY_BUT_BACKTICK+ BACKTICK {System.out.println("LITTERAL: " + $text);}
  ;

INLINE_INTERNAL_TARGET
    : UNDERSCORE BACKTICK ANY_BUT_BACKTICK+ BACKTICK {System.out.println("INLINE_INTERNAL_TARGET: " + $text);}
    ;

FOOTNOTE_REFERENCE
    : L_BRACKET ANY_BUT_BRACKET+ R_BRACKET UNDERSCORE {System.out.println("FOOTNOTE_REFERENCE: " + $text);}
    ;


HYPERLINK_REFERENCE
  : BACKTICK ANY_BUT_BACKTICK+ BACKTICK UNDERSCORE {System.out.println("HYPERLINK_REFERENCE (long): " + $text);}
  | ANY_BUT_ENDLINK+ UNDERSCORE {System.out.println("HYPERLINK_REFERENCE (short): " + $text);}
  ;

WS  
  : (' '|'\t')+ {$channel=HIDDEN;}
  ; 

NEWLINE
  : '\r'? '\n' {$channel=HIDDEN;}
  ;


///////////////
// FRAGMENTS //
///////////////

fragment ANY_BUT_PIPE
  : ESC PIPE
  | ~(PIPE|'\n'|'\r')
  ;
fragment ANY_BUT_BRACKET
  : ESC R_BRACKET
  | ~(R_BRACKET|'\n'|'\r')
  ;
fragment ANY_BUT_STAR
  : ESC STAR
  | ~(STAR|'\n'|'\r')
  ;
fragment ANY_BUT_BACKTICK
  : ESC BACKTICK
  | ~(BACKTICK|'\n'|'\r')
  ;
fragment ANY_BUT_ENDLINK
  : ~(UNDERSCORE|' '|'\t'|'\n'|'\r')
  ;



fragment ESC
  : '\\'
  ;
fragment STAR
  : '*'
  ;
fragment BACKTICK
  : '`'
  ;
fragment PIPE
  : '|'
  ;
fragment L_BRACKET
  : '['
  ;
fragment R_BRACKET
  : ']'
  ;
fragment UNDERSCORE
  : '_'
  ;

Грамматика нормально работает для inline_markup, но обычный_текст не соответствует.

Вот мой тестовый класс:

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.Tree;

public class Test {

    public static void main(String[] args) throws RecognitionException, IOException {

        InputStream is = Test.class.getResourceAsStream("test.rst");
        Reader r = new InputStreamReader(is);
        StringBuilder source = new StringBuilder();
        char[] buffer = new char[1024];
        int readLenght = 0;
        while ((readLenght = r.read(buffer)) > 0) {
            if (readLenght < buffer.length) {
                source.append(buffer, 0, readLenght);
            } else {
                source.append(buffer);
            }
        }
        r.close();
        System.out.println(source.toString());

        ANTLRStringStream in = new ANTLRStringStream(source.toString());
        RstLexer lexer = new RstLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        RstParser parser = new RstParser(tokens);
        RstParser.file_return out = parser.file();
        System.out.println(((Tree)out.getTree()).toStringTree());
    }
}

И входной файл, который я использую:

In `Figure 17-6`_, we have positioned ``before_ptr`` so that it points to the element 
*before* the insert point. The variable ``after_ptr`` points to the |element| *after* the 
insert. In other words, `we are going`_ to put_ our new element **in between** ``before_ptr`` 
and ``after_ptr``.

И я получаю этот вывод:

HYPERLINK_REFERENCE (short): 7-6`_
line 1:2 mismatched character ' ' expecting '_'
line 1:10 mismatched character ' ' expecting '_'
line 1:18 mismatched character ' ' expecting '_'
line 1:21 mismatched character ' ' expecting '_'
line 1:26 mismatched character ' ' expecting '_'
line 1:37 mismatched character ' ' expecting '_'
LITTERAL: `before_ptr`
line 1:86 no viable alternative at character '\r'
line 1:55 mismatched character ' ' expecting '_'
line 1:60 mismatched character ' ' expecting '_'
line 1:63 mismatched character ' ' expecting '_'
line 1:70 mismatched character ' ' expecting '_'
line 1:73 mismatched character ' ' expecting '_'
line 1:77 mismatched character ' ' expecting '_'
line 1:85 mismatched character ' ' expecting '_'
EMPHASIS: *before*
line 2:12 mismatched character ' ' expecting '_'
line 2:19 mismatched character ' ' expecting '_'
line 2:26 mismatched character ' ' expecting '_'
LITTERAL: `after_ptr`
line 2:30 mismatched character ' ' expecting '_'
line 2:39 mismatched character ' ' expecting '_'
line 2:90 no viable alternative at character '\r'
line 2:60 mismatched character ' ' expecting '_'
line 2:63 mismatched character ' ' expecting '_'
line 2:67 mismatched character ' ' expecting '_'
line 2:77 mismatched character ' ' expecting '_'
line 2:85 mismatched character ' ' expecting '_'
line 2:89 mismatched character ' ' expecting '_'
line 3:7 mismatched character ' ' expecting '_'
line 3:10 mismatched character ' ' expecting '_'
line 3:16 mismatched character ' ' expecting '_'
line 3:23 mismatched character ' ' expecting '_'
line 3:27 mismatched character ' ' expecting '_'
line 3:31 mismatched character ' ' expecting '_'
line 3:42 mismatched character ' ' expecting '_'
line 3:51 mismatched character ' ' expecting '_'
line 3:55 mismatched character ' ' expecting '_'
line 3:63 mismatched character ' ' expecting '_'
line 3:94 mismatched character '\r' expecting '*'
line 4:3 mismatched character ' ' expecting '_'
line 4:18 no viable alternative at character '\r'
line 4:18 mismatched character '\r' expecting '_'
HYPERLINK_REFERENCE (short): oing`_
HYPERLINK_REFERENCE (short): ut_
EMPHASIS: *in between*
LITTERAL: `after_ptr`
BR.recoverFromMismatchedToken
line 0:-1 mismatched input '<EOF>' expecting NEWLINE
null

Можете ли вы указать на мои ошибки? (синтаксический анализатор работает для встроенной разметки без ошибок, когда я добавляю filter = true; опция к грамматике)

Robin

Ответы [ 2 ]

6 голосов
/ 06 июня 2011

Вот краткая демонстрация того, как вы могли бы проанализировать этот reStructeredText. Обратите внимание, что он просто обрабатывает второстепенный набор всех доступных синтаксисов разметки, и, добавляя к нему больше, вы будете влиять на существующие правила синтаксического анализатора / лексера: так что многое, много больше работы предстоит сделать!

Демо

grammar RST;

options {
  output=AST;
  backtrack=true;
  memoize=true;
}

tokens {
  ROOT;
  PARAGRAPH;
  INDENTATION;
  LINE;
  WORD;
  BOLD;
  ITALIC;
  INTERPRETED_TEXT;
  INLINE_LITERAL;
  REFERENCE;
}

parse
  :  paragraph+ EOF -> ^(ROOT paragraph+)
  ;

paragraph
  :  line+ -> ^(PARAGRAPH line+)
  |  Space* LineBreak -> /* omit line-breaks between paragraphs from AST */
  ;

line
  :  indentation text+ LineBreak -> ^(LINE text+)
  ;

indentation
  :  Space* -> ^(INDENTATION Space*)
  ;

text
  :  styledText
  |  interpretedText
  |  inlineLiteral
  |  reference
  |  Space
  |  Star
  |  EscapeSequence
  |  Any
  ;

styledText
  :  bold
  |  italic
  ;

bold
  :  Star Star boldAtom+ Star Star -> ^(BOLD boldAtom+)
  ;  

italic
  :  Star italicAtom+ Star -> ^(ITALIC italicAtom+)
  ;

boldAtom
  :  ~(Star | LineBreak)
  |  italic
  ;

italicAtom
  :  ~(Star | LineBreak)
  |  bold
  ;

interpretedText
  :  BackTick interpretedTextAtoms BackTick -> ^(INTERPRETED_TEXT interpretedTextAtoms)
  ;

interpretedTextAtoms
  :  ~BackTick+
  ;

inlineLiteral
  :  BackTick BackTick inlineLiteralAtoms BackTick BackTick -> ^(INLINE_LITERAL inlineLiteralAtoms)
  ;

inlineLiteralAtoms
  :  inlineLiteralAtom+
  ;

inlineLiteralAtom
  :  ~BackTick
  |  BackTick ~BackTick
  ;

reference
  :  Any+ UnderScore -> ^(REFERENCE Any+)
  ;

UnderScore
  :  '_'
  ;

BackTick
  :  '`'
  ;

Star
  :  '*'
  ;

Space
  :  ' ' 
  |  '\t'
  ;

EscapeSequence
  :  '\\' ('\\' | '*')
  ;

LineBreak
  :  '\r'? '\n'
  |  '\r'
  ;

Any
  :  .
  ;

Когда вы генерируете синтаксический анализатор и лексер из приведенного выше, и позволяете ему анализировать следующий входной файл:

***x*** **yyy** *zz* *
a b c

P2 ``*a*`b`` `q`
Python_

(обратите внимание на разрыв задней линии!)

парсер выдаст следующий AST:

enter image description here

EDIT

График можно создать, запустив этот класс:

import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import org.antlr.stringtemplate.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String source =
        "***x*** **yyy** *zz* *\n" +
        "a b c\n" +
        "\n" +
        "P2 ``*a*`b`` `q`\n" +
        "Python_\n";
    RSTLexer lexer = new RSTLexer(new ANTLRStringStream(source));
    RSTParser parser = new RSTParser(new CommonTokenStream(lexer));
    CommonTree tree = (CommonTree)parser.parse().getTree();
    DOTTreeGenerator gen = new DOTTreeGenerator();
    StringTemplate st = gen.toDOT(tree);
    System.out.println(st);
  }
}

или если ваш источник взят из файла, выполните:

RSTLexer lexer = new RSTLexer(new ANTLRFileStream("test.rst"));

или

RSTLexer lexer = new RSTLexer(new ANTLRFileStream("test.rst", "???"));

где "???" - кодировка вашего файла.

Класс выше будет печатать AST как файл DOT на консоли. Вы можете использовать DOT Viewer для отображения AST. В этом случае я разместил изображение, созданное kgraphviewer . Но есть гораздо больше зрителей вокруг . Хорошая онлайн-версия - , эта , которая, кажется, использует kgraphviewer под "капотом". Удачи!

4 голосов
/ 30 мая 2011

Робин писал:

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

Я долженпризнайте, что я не знаком с этим языком разметки, но, похоже, он напоминает разметку BB-кода или Wiki, которые нелегко переводить в грамматику (ANTLR)!Эти языки не позволяют легко быть токенизированными, поскольку это зависит от того, где эти токены встречаются.Пробелы иногда имеют особое значение (со списками определений).Так что нет, это совсем не легко, ИМО.Поэтому, если вы хотите познакомиться с ANTLR (или генератором синтаксического анализатора в целом), я весьма рекомендую выбрать что-то еще для анализа.

Робин написал:

Может ли кто-нибудь указать на мои ошибки и, возможно, дать мне подсказку о том, как сопоставить обычный текст?

Вы должны сначала понять, чтоANTLR создает лексер (токенизатор) и парсер.Правила Lexer начинаются с заглавной буквы, а правила синтаксического анализатора начинаются со строчной буквы.Парсер может работать только с токенами (объектами, созданными по правилам лексера).Чтобы упорядочить ситуацию, вы не должны не использовать токены-литералы в правилах синтаксического анализатора (см. Правило q в грамматике ниже).Кроме того, метасимвол ~ (отрицание) имеет различное значение в зависимости от того, где он используется (в правиле синтаксического анализатора или лексера).

Примите следующую грамматику:

p : T;
q : ~'z';

T : ~'x';
U : 'y';

ANTLR сначала «переместит» литерал 'z' в правило лексера следующим образом:

p : T;
q : ~RANDOM_NAME;

T : ~'x';
U : 'y';
RANDOM_NAME : 'z';

(имя RANDOM_NAME не используется, но это не имеет значения).Теперь правило синтаксического анализатора q не не соответствует любому символу, кроме 'z'!Отрицание внутри правила синтаксического анализатора отменяет токен (или правило лексера).Таким образом, ~RANDOM_NAME будет соответствовать правилу лексера T или правилу лексера U.

Внутри правил лексера ~ отменяет (один!) Символы.Таким образом, правило лексера T будет соответствовать любому символу в диапазоне \u0000 .. \uFFFF, кроме 'x'.Обратите внимание, что следующее: ~'ab' недопустимо в правиле лексера: вы можете отвергать только одиночные наборы символов.

Итак, все эти ~'???' внутри правил вашего синтаксического анализатора неверны (неправильно, как в: они неведет себя так, как вы ожидаете).

Робин писал:

Есть ли способ установить приоритет в правилах грамматики?Возможно, это может быть лидерство.

Да, порядок сверху вниз в правилах как лексеров, так и парсеров (где верх имеет наивысший приоритет).Допустим, parse является точкой входа в вашу грамматику:

parse
  :  p
  |  q
  ;

, затем сначала будет пробоваться p, а в случае неудачи q будет пытаться найти соответствие.

Что касается правил лексера, то правила, которые, например, являются ключевыми словами, сопоставляются перед правилом, которое могло бы соответствовать указанным ключевым словам:

// first keywords:
WHILE : 'while';
IF    : 'if'
ELSE  : 'else';

// and only then, the identifier rule: 
ID    : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;
...