我正在解析定义两种语句的脚本语言;控制语句和非控制语句。非控制语句始终以';'
结尾,而控制语句可能以';'
或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
所以我使用普通的词法分析器规则来隐藏空格。
这在所有情况下都可以正常工作,但是我需要使用EOL
来查找控制语句的终止的情况,但是使用上面的语法,所有EOL
都被隐藏并且不会用于控制声明规则。
有没有办法改变我的语法,以便我可以跳过所有EOL
,但需要终止部分控制语句?
答案 0 :(得分:2)
找到了解决此问题的一种方法。
这个想法是将EOL转移到一个隐藏的频道,而另一个我不想在另一个隐藏频道中看到的东西(如空格和评论)。然后,当EOL应该出现并检查之前的令牌通道时,我会使用一些代码来回溯令牌(因为它们已被消耗)。如果我在从普通频道遇到某些内容之前在EOL频道上找到了某些内容,那就没关系。
看起来像这样:
更改了词法分析器规则:
@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)
;
我还将所有其他HIDDEN频道(评论)转移到OTHER_CHANNEL
。
然后我更改了规则end_control_statment
:
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);
}
}
人们可以坚持普通的隐藏频道,但是需要在回溯的同时跟踪频道和令牌,这样可能会更容易......
希望这可以帮助其他人处理这类问题...