ANTLR3: соответствует ровно одному символу в токене - PullRequest
1 голос
/ 16 апреля 2020

Я новичок ie в ANTLR и работаю над парсером, использующим ANTLR3, но у меня возникли проблемы в следующей ситуации. В тексте, который мы анализируем, может быть несколько ситуаций, в которых встречается символ ^. Однако есть один особый случай, когда за «^» следует ровно один символ. Это происходит в строках:

  1. 'MyText' ^ M
  2. ^ MyValue

В первой ситуации '^ M' является частью строки, где ^ M обозначает 13 гекс, но во втором это не так; там это указатель указатель. Вторая ситуация описана в правилах грамматики (символ ^ используется в нескольких правилах).

Если я решу ее с помощью следующих токенов, произойдет сбой, потому что '^ MyValue' маркируется в '^ M 'и' yValue '. Однако я хочу, чтобы токен ControlChar использовался только в том случае, если после ^ стоит ровно один символ. В противном случае его следует игнорировать и не маркировать, чтобы его можно было использовать в грамматике.

Pointer                 : '^'
                        ;
QuotedString            : '\'' ('\'\'' | ~('\''))* '\''
                        ;
TkIdentifier            : (Alpha | '_') (Alpha | Digit | '_')*
                        ;
ControlString           : Controlchar (Controlchar)*
                        ;
fragment
Controlchar             : '#' Digitseq
                        | '#' '$' Hexdigitseq
                        | '^' Alpha
                        ;
fragment
Alpha                   : 'a'..'z'
                        | 'A'..'Z'
                        ;
fragment
Digit                   : '0'..'9'
                        ;

Итак, мой вопрос. Как я могу указать ANTLR, что '^' Alpha сопоставляется только в том случае, если за этим символом стоит ровно одна Альфа, и в противном случае оставить в тексте '^' и маркировать Альфа, Цифры или '_' как токен TkIdentifier?

Например, лексер должен создать следующие токены:

^Foo -> Pointer TkIdentifier
^F oo -> ControlChar TkIdentifier
^ F oo -> Pointer TkIdentifier TkIdentifier
Foo^M -> TkIdentifier ControlChar
Foo ^ M -> TkIdentifier Pointer TkIdentifier
Foo ^M -> TkIdentifier ControlChar
Foo^ M -> TkIdentifier Pointer TkIdentifier

'Text'^M -> QuotedString ControlChar
'Text' ^M -> QuotedString ControlChar
'Text' ^ M -> QuotedString Pointer TkIdentifier
^M'Text' -> ControlChar QuotedString
^M 'Text' -> ControlChar QuotedString
^ M'Text' -> Pointer TkIdentifier QuotedString

1 Ответ

0 голосов
/ 17 апреля 2020

В этом случае вам нужно будет использовать целевой спецификатор c кода внутри предиката в вашей грамматике.

Что вам нужно сделать, это так: всякий раз, когда Лексер натыкается на ^, ему придется посмотреть на 2 символа впереди в потоке. Если эти 2 символа являются словом и не словом, он создаст токен Controlchar, включая Alpha, следующий за ^. Если нет, создайте Pointer из ^.

Для ANTLR3 с Java в качестве целевого языка, это может выглядеть так:

// Cannot be a fragment now, because we're changing the `type` in certain cases. And because it is
// no fragment any more, it has to come before the `ControlString` rule.
Controlchar
 : '^' ( // Execute the predicate, which looks ahead 2 chars and passes if 
         // these 2 chars are a word and a non-word
         {((char)input.LA(1) + "" + (char)input.LA(2)).matches("\\w\\W")}?=> 
         // If the predicate is true, match a single `Alpha`
         Alpha
       |  // If the predicate failed, change the type of this token to a `Pointer`
         {$type=Pointer;}
       )
 | '#' Digitseq
 | '#' '$' Hexdigitseq
 ;

ControlString
 : Controlchar+
 ;

Я быстро запустил test:

import org.antlr.runtime.*;

public class Main {

    public static void main(String[] args) throws Exception {

        String[] tests = {
                "^Foo",
                "^F oo",
                "^ F oo",
                "Foo^M",
                "Foo ^ M",
                "Foo ^M",
                "Foo^ M",
                "'Text'^M",
                "'Text' ^M",
                "'Text' ^ M",
                "^M'Text'",
                "^M 'Text'",
                "^ M'Text'",
                "^Q^E^D"
        };

        for (String test : tests) {

            TLexer lexer = new TLexer(new ANTLRStringStream(test));
            CommonTokenStream tokenStream = new CommonTokenStream(lexer);
            tokenStream.fill();

            System.out.printf("\ntest: %-15s tokens: ", test);

            for (Token t : tokenStream.getTokens()) {
                if (t.getType() != -1) {
                    System.out.printf(" %s", TParser.tokenNames[t.getType()]);
                }
            }
        }
    }
}

, который напечатал:

test: ^Foo            tokens:  Pointer TkIdentifier
test: ^F oo           tokens:  Controlchar TkIdentifier
test: ^ F oo          tokens:  Pointer TkIdentifier TkIdentifier
test: Foo^M           tokens:  TkIdentifier Controlchar
test: Foo ^ M         tokens:  TkIdentifier Pointer TkIdentifier
test: Foo ^M          tokens:  TkIdentifier Controlchar
test: Foo^ M          tokens:  TkIdentifier Pointer TkIdentifier
test: 'Text'^M        tokens:  QuotedString Controlchar
test: 'Text' ^M       tokens:  QuotedString Controlchar
test: 'Text' ^ M      tokens:  QuotedString Pointer TkIdentifier
test: ^M'Text'        tokens:  Controlchar QuotedString
test: ^M 'Text'       tokens:  Controlchar QuotedString
test: ^ M'Text'       tokens:  Pointer TkIdentifier QuotedString
test: ^Q^E^D          tokens:  ControlString

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

// Place this in the top of your grammar definition
@lexer::members {
  private boolean isControlchar() {
    // TODO 
    //  - check if there are actually 2 chars ahead and not an EOF
    //  - perhaps something else than a regex match here
    return ((char)input.LA(1) + "" + (char)input.LA(2)).matches("\\w\\W");
  }
}

...

Controlchar
 : '^' ( {isControlchar()}?=> Alpha
       | {$type=Pointer;}
       )
 | '#' Digitseq
 | '#' '$' Hexdigitseq
 ;

Поскольку type токена изменяется программно, правило лексера, такое как Controlstring : Controlchar+;, также будет соответствовать ^^^ (три Pointer токена). Если возможно, вы можете создать вместо этого правило парсера:

controlstring
 : Controlchar+
 ;
...