Parsing Antlr4:如何在语法中隐藏和使用标记

Parsing Antlr4:如何在语法中隐藏和使用标记,parsing,whitespace,antlr4,Parsing,Whitespace,Antlr4,我正在解析一种脚本语言,它定义了两种类型的语句;控制语句和非控制语句。非控制语句始终以“;”结尾,而控制语句可能以“”;”结尾或EOL('\n')。语法的一部分如下所示: script : statement* EOF ; statement : control_statement | no_control_statement ; control_statement : if_then_control_statement

我正在解析一种脚本语言,它定义了两种类型的语句;控制语句和非控制语句。非控制语句始终以
“;”结尾,而控制语句可能以“
”;”结尾
EOL
('\n')。语法的一部分如下所示:

script
    :   statement* EOF
    ;

statement
    :   control_statement
    |   no_control_statement
    ;

control_statement
    :   if_then_control_statement
    ;

if_then_control_statement
    :   IF expression THEN end_control_statment
        ( statement ) *
        ( ELSEIF expression THEN end_control_statment ( statement )* )*
        ( ELSE end_control_statment ( statement )* )?
        END IF end_control_statment
    ;

no_control_statement
    :   sleep_statement
    ;

sleep_statement
    :   SLEEP expression END_STATEMENT
    ;

end_control_statment
    :   END_STATEMENT
    |   EOL
    ;

END_STATEMENT
    :   ';'
    ;

ANY_SPACE
    :   ( LINE_SPACE | EOL )    ->  channel(HIDDEN)
    ;

EOL
    :   [\n\r]+
    ;

LINE_SPACE
    :   [ \t]+
    ;
在脚本语言的所有其他方面,我从不关心
EOL
,所以我使用普通的lexer规则来隐藏空白

这在所有情况下都可以正常工作,但在我需要使用
EOL
来查找控制语句的终止的情况下除外,但是使用上面的语法,所有
EOL
都是隐藏的,不在控制语句规则中使用


有没有一种方法可以改变我的语法,这样我就可以跳过所有的
EOL
,但可以跳过终止部分控制语句所需的语法?

找到了一种方法来处理这个问题

这个想法是把EOL转移到一个隐藏的频道,而其他我不想在另一个隐藏频道看到的东西(比如空格和评论)。然后,当一个EOL应该出现时,我使用一些代码来回溯令牌,并检查以前的令牌通道(因为它们已经被使用)。如果在我遇到普通频道的内容之前,我在EOL频道上找到了一些内容,那么就可以了

看起来是这样的:

script
    :   statement* EOF
    ;

statement
    :   control_statement
    |   no_control_statement
    ;

control_statement
    :   if_then_control_statement
    ;

if_then_control_statement
    :   IF expression THEN end_control_statment
        ( statement ) *
        ( ELSEIF expression THEN end_control_statment ( statement )* )*
        ( ELSE end_control_statment ( statement )* )?
        END IF end_control_statment
    ;

no_control_statement
    :   sleep_statement
    ;

sleep_statement
    :   SLEEP expression END_STATEMENT
    ;

end_control_statment
    :   END_STATEMENT
    |   EOL
    ;

END_STATEMENT
    :   ';'
    ;

ANY_SPACE
    :   ( LINE_SPACE | EOL )    ->  channel(HIDDEN)
    ;

EOL
    :   [\n\r]+
    ;

LINE_SPACE
    :   [ \t]+
    ;
更改了lexer规则:

@lexer::members {
    public static int EOL_CHANNEL = 1;
    public static int OTHER_CHANNEL = 2;
}

...

EOL
  : '\r'? '\n'  ->  channel(EOL_CHANNEL)
  ;

LINE_SPACE
  : [ \t]+  ->  channel(OTHER_CHANNEL)
  ;
我还将所有其他隐藏频道(评论)转移到
其他频道
。 然后我更改了规则
end\u control\u station

end_control_statment
  : END_STATEMENT
  | { isEOLPrevious() }?
  ;
并补充说

@parser::members {
  public static int EOL_CHANNEL = 1;
  public static int OTHER_CHANNEL = 2;

  boolean isEOLPrevious()
  {
        int idx = getCurrentToken().getTokenIndex();
        int ch;

        do
        {
            ch = getTokenStream().get(--idx).getChannel();
        }
        while (ch == OTHER_CHANNEL);

        // Channel 1 is only carrying EOL, no need to check token itself
        return (ch == EOL_CHANNEL);
     }
}
你可以坚持使用普通的隐藏通道,但是在回溯时需要同时跟踪通道和令牌,所以这可能会更容易一些

希望这能帮助其他人处理这类问题