Как получить правило анализатора Antlr для чтения как из скрытого, так и по умолчанию канала? - PullRequest
6 голосов
/ 21 апреля 2011

Я использую обычное разделение пробелов в скрытом канале, но у меня есть одно правило, в котором я хотел бы включить любые пробелы для дальнейшей обработки, но любой найденный пример требует очень странного ручного кодирования.

Есть линет простого варианта чтения из нескольких каналов, например, возможность поставить пробел с самого начала.

Пример.это правило лексера WhiteSpace

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

И это мое правило, в котором я хотел бы включить пробелы

raw :   '{'? (~('{'))*;

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

Я надеялся на пример синтаксиса {$channel==DEFAULT || $channel==HIDDEN}, но не смог найти ни одного.

Моя цель будет C #, но япри необходимости может переписать примеры Java.

Ответы [ 3 ]

4 голосов
/ 21 апреля 2011

AFAIK, это невозможно.Однако вы можете расширить UnbufferedTokenStream, чтобы изменить channel во время анализа.Вы не можете использовать CommonTokenStream, поскольку он буферизует переменное количество токенов (и в буфере могут быть токены, которые находятся на неправильном канале!).Обратите внимание, что вам нужен как минимум ANTLR 3.3: в предыдущих версиях UnbufferedTokenStream еще не было включено.

Допустим, вы хотите проанализировать (и отобразить) буквы как в верхнем, так и в верхнем регистре.Буквы верхнего регистра помещаются на канал HIDDEN, поэтому по умолчанию анализируются только буквы нижнего регистра.Однако, когда синтаксический анализатор наталкивается на нижний регистр "q", мы хотим перейти на канал HIDDEN.Разобравшись на канале HIDDEN, мы хотим, чтобы "Q" снова вернул нас к DEFAULT_CHANNEL.

Итак, при разборе источника "aAbBcqCdDQeE", сначала "a", "b" и"c" печатаются, затем канал изменяется, затем "C" и "D" печатаются, затем канал снова изменяется, и, наконец, "e" выводится на консоль.

Вот ANTLRграмматика, которая делает это:

ChannelDemo.g

grammar ChannelDemo;

@parser::members {
  private void handle(String letter) {
    if("Q".equals(letter)) {
      ((ChangeableChannelTokenStream)input).setChannel(Token.DEFAULT_CHANNEL);
    }
    else if("q".equals(letter)) {
      ((ChangeableChannelTokenStream)input).setChannel(HIDDEN);
    }
    else {
      System.out.println(letter);
    }
  }
}

parse
  :  any* EOF
  ;

any
  :  letter=(LOWER | UPPER) {handle($letter.getText());}
  ;

LOWER
  :  'a'..'z'
  ;

UPPER
  :  'A'..'Z' {$channel=HIDDEN;}
  ;

А вот пользовательский класс потока токенов:

ChangeableChannelTokenStream.java

import org.antlr.runtime.*;

public class ChangeableChannelTokenStream extends UnbufferedTokenStream {

    public ChangeableChannelTokenStream(TokenSource source) {
        super(source);
    }

    public Token nextElement() {
        Token t = null;
        while(true) {
            t = super.tokenSource.nextToken();
            t.setTokenIndex(tokenIndex++);
            if(t.getChannel() == super.channel) break;
        }
        return t;
    }

    public void setChannel(int ch) {
        super.channel = ch;
    }
}

Инебольшой класс Main для проверки всего этого:

Main.java

import org.antlr.runtime.*;

public class Main {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("aAbBcqCdDQeE");
        ChannelDemoLexer lexer = new ChannelDemoLexer(in);
        ChangeableChannelTokenStream tokens = new ChangeableChannelTokenStream(lexer);
        ChannelDemoParser parser = new ChannelDemoParser(tokens);
        parser.parse();
    }
}

Наконец, сгенерируйте лексер / парсер (1), скомпилируйте все исходные файлы (2) и запустите класс Main(3):

1

java -cp antlr-3.3.jar org.antlr.Tool ChannelDemo.g

2

javac -cp antlr-3.3.jar *.java

3 (* nix)

java -cp .:antlr-3.3.jar Main

3 (Windows)

java -cp .;antlr-3.3.jar Main

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

a
b
c
C
D
e

EDIT

Вы можете включить класс в файл грамматики следующим образом:

grammar ChannelDemo;

@parser::members {
  private void handle(String letter) {
    if("Q".equals(letter)) {
      ((ChangeableChannelTokenStream)input).setChannel(Token.DEFAULT_CHANNEL);
    }
    else if("q".equals(letter)) {
      ((ChangeableChannelTokenStream)input).setChannel(HIDDEN);
    }
    else {
      System.out.println(letter);
    }
  }

  public static class ChangeableChannelTokenStream extends UnbufferedTokenStream {

    private boolean anyChannel;

    public ChangeableChannelTokenStream(TokenSource source) {
      super(source);
      anyChannel = false;
    }

    @Override
    public Token nextElement() {
      Token t = null;
      while(true) {
        t = super.tokenSource.nextToken();
        t.setTokenIndex(tokenIndex++);
        if(t.getChannel() == super.channel || anyChannel) break;
      }
      return t;
    }

    public void setAnyChannel(boolean enable) {
      anyChannel = enable;
    }

    public void setChannel(int ch) {
      super.channel = ch;
    }
  }
}

parse
  :  any* EOF
  ;

any
  :  letter=(LOWER | UPPER) {handle($letter.getText());}
  |  STAR                   {((ChangeableChannelTokenStream)input).setAnyChannel(true);}
  ;

STAR
  :  '*'
  ;

LOWER
  :  'a'..'z'
  ;

UPPER
  :  'A'..'Z' {$channel=HIDDEN;}
  ;

Синтаксический анализатор, сгенерированный из приведенной выше грамматики, включит чтение со всех каналов при обнаружении "*".Таким образом, при разборе "aAbB*cCdDeE":

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    ANTLRStringStream in = new ANTLRStringStream("aAbB*cCdDeE");
    ChannelDemoLexer lexer = new ChannelDemoLexer(in);
    ChannelDemoParser.ChangeableChannelTokenStream tokens =
        new ChannelDemoParser.ChangeableChannelTokenStream(lexer);
    ChannelDemoParser parser = new ChannelDemoParser(tokens);
    parser.parse();
  }
}

выводится следующее:

a
b
c
C
d
D
e
E
0 голосов
/ 24 сентября 2013

В Antler 4 Я использую простое решение. Я не проверял это в Antlr 3. Это C #, но вы можете легко перевести его на Java.

  1. Изменить parser1.g4 следующим образом:

    parser grammar Parser1;
    
    options { tokenVocab=Lexer1; }
    
    startRule
    @init { SetWhiteSpacesAcceptence(false); } 
        : (componentWithWhiteSpaces | componentWithoutWhiteSpaces)* EOF
    ;
    
    componentWithWhiteSpaces : { SetWhiteSpacesAcceptence(true); } 
                                component1 component2 component3 
                                { SetWhiteSpacesAcceptence(false); } 
    ;
    
    componentWithoutWhiteSpaces : component4 component5 component6 
    
  2. Изменить lexer1.g4 следующим образом:

    lexer grammar Lexer1;
    WS : [ \t\r\n] { if( this.IsWhiteSpacesAccepted() ) Skip(); };
    
  3. Расширить Parser1 класс следующим образом:

    class MyParser : Parser1
    {
        public void SetWhiteSpacesAcceptence(bool isAccept)
        {
            if (_input != null && _input.TokenSource != null)
            {
                if (_input.TokenSource is MyLexer)
                {
                    MyLexer lexer = _input.TokenSource as MyLexer;
                    if (lexer != null)
                        lexer.SetWhiteSpacesAcceptence(isAccept);
                }
            }
        }
    
        public bool IsWhiteSpacesAccepted()
        {
            if (_input != null && _input.TokenSource != null)
            {
                if (_input.TokenSource is MyLexer)
                {
                    MyLexer lexer = _input.TokenSource as MyLexer;
                    if (lexer != null)
                        return lexer.IsWhiteSpacesAccepted();
                }
            }
    
            return false;
        }
    }
    
  4. Расширить Lexer1 класс следующим образом:

    class MyLexer : Lexer1
    {
        private bool isWhiteSpacesAccepted;
    
        public void SetWhiteSpacesAcceptence(bool isAccept) { isWhiteSpacesAccepted = isAccept }
    
        public bool IsWhiteSpacesAccepted() { return isWhiteSpacesAccepted; }
    }
    
  5. Теперь Main работает следующим образом:

    static void Main()
    {
        AntlrFileStream input = new AntlrFileStream("pathToInputFile");
        MyLexer lexer = new MyLexer(input);
        UnbufferedTokenStream tokens = new UnbufferedTokenStream(lexer);
        MyParser parser = new MyParser(tokens);
    
        parser.startRule();
    }
    
0 голосов
/ 13 августа 2012

Возможно, вам следует рассмотреть вопрос о том, чтобы сделать пробел частью вашей грамматики.Но зачем засорять вашу грамматику такой неважной информацией?Ну, потому что это НЕ Неважно.Новая строка имеет значение в определенных контекстах.Если вам нужна поддержка IDE, например, с языкового сервера Visual Studio, вам нужно указать грамматику языка без всех наворотов низкоуровневой настройки ANTLR.

...