ANTLR4:使用关键字(别名?)清理语法和树
ANTLR4 : clean grammar and tree with keywords (aliases ?)
我正在寻找一个简单问题的解决方案。
例子:
SELECT date, date(date)
FROM date;
这是一个相当愚蠢的示例,其中一个 table、它的列和一个函数都具有名称 "date"。
我的语法片段(非常简化):
simple_select
: SELECT selected_element (',' selected_element) FROM from_element ';'
;
selected_element
: function
| REGULAR_WORD
;
function
: REGULAR_WORD '(' function_argument ')'
;
function_argument
: REGULAR_WORD
;
from_element
: REGULAR_WORD
;
DATE: D A T E;
FROM: F R O M;
SELECT: S E L E C T;
REGULAR_WORD
: (SIMPLE_LETTER) (SIMPLE_LETTER | '0'..'9')*
;
fragment SIMPLE_LETTER
: 'a'..'z'
| 'A'..'Z'
;
DATE 是一个关键字(它在语法的其他地方使用)。
如果我想让它被我的语法识别为一个普通词,这里是我的解决方案:
1) 我把它加到我用过的地方 REGULAR_WORD, 旁边。
示例:
selected_element
: function
| REGULAR_WORD
| DATE
;
=> 我不想要这个解决方案。我不仅有 "DATE" 作为关键字,而且我有很多使用 REGULAR_WORD 的规则,所以我需要添加许多(50+)关键字的列表,如 DATE 到许多(20+)解析器规则:这绝对是丑陋的。
优点:做一棵干净的树
缺点:使语法变脏
2) 我在两者之间使用解析器规则来获取所有这些关键字,然后,我用该解析器规则替换每个出现的 REGULAR_WORD 。
示例:
word
: REGULAR_WORD
| DATE
;
selected_element
: function
| word
;
=> 我也不想要这个解决方案,因为它在树中添加了一个解析器规则并污染了信息(我不想知道 "date" 是一个词,我想知道它是一个 selected_element、一个函数、一个 function_argument 或一个 from_element ...
优点:语法清晰
缺点:做一棵脏树
无论哪种方式,我都有一个脏树或脏语法。有没有办法让两者都干净?
我寻找了别名、等效的解析器片段,但 ANTLR4 似乎没有?
谢谢,祝你有个愉快的一天!
Antlr4 grammar repository SQL 方言有四种不同的语法,所有四种都使用您的第二种策略。所以似乎 Antlr4 sql 语法作者之间达成了共识。考虑到 Antlr4 词法分析器的设计,我认为没有更好的解决方案。
正如您所说,这会导致整个解析树中出现一些噪音,但相关的非终结符(function
、selected_element
等)肯定存在并且确实存在在我看来,将单元产生式从解析树中折叠出来并不难。
据我了解,在设计 Antlr4 时,决定只自动生成完整的解析树,因为压缩 ("abstract") 语法树的设计太特殊了,不适合语法数字用户线。所以如果你觉得 AST 更方便,你有责任自己生成一个。这通常是直截了当的,尽管它涉及很多样板文件。
其他解析器生成器确实有可以处理 "semireserved keywords" 的机制。特别是,作为 Sqlite 项目一部分的 Lemon 解析器生成器包含一个 %fallback
声明,该声明允许您指定一个或多个标记应在没有语法规则允许的上下文中自动重新分类用过的。不幸的是,Lemon 不会生成 Java 个解析器。
另一个类似的选择是使用支持 "scannerless" 解析的解析器生成器。此类解析器通常使用像 Earley/GLL/GLR 这样的算法,能够解析任意 CFG,以解决比 LALR(1) 等固定先行算法可以方便地支持更多先行的需求。
这就是所谓的关键字作为标识符的问题,之前已经讨论过很多次了。例如,我 6 年前就在 ANTLR mailing list. But also here at Whosebug there are questions touching this area, for instance Trying to use keywords as identifiers in ANTLR4; not working.
中问过一个类似的问题
Terence Parr 在 2008 年写了一篇 wiki article for ANTLR3,其中简要描述了 2 种可能的解决方案:
This grammar allows "if if call call;" and "call if;".
grammar Pred;
prog: stat+ ;
stat: keyIF expr stat
| keyCALL ID ';'
| ';'
;
expr: ID
;
keyIF : {input.LT(1).getText().equals("if")}? ID ;
keyCALL : {input.LT(1).getText().equals("call")}? ID ;
ID : 'a'..'z'+ ;
WS : (' '|'\n')+ {$channel=HIDDEN;} ;
You can make those semantic predicates more efficient by intern'ing those strings so that you can do integer comparisons instead of string compares.
The other alternative is to do something like this
identifier : KEY1 | KEY2 | ... | ID ;
which is a set comparison and should be faster.
通常,正如@rici 已经提到的,人们更喜欢将所有关键字保留在自己的规则中并将其添加到普通标识符规则(允许此类关键字的地方)的解决方案。
wiki 中的其他解决方案可以针对任何关键字进行推广,方法是在 ID
词法分析器规则的操作中使用查找 table/list,该规则用于检查给定字符串是否是一个关键字。此解决方案不仅速度较慢,而且还牺牲了解析器语法的清晰度,因为您不能再在解析器规则中使用关键字标记。
我正在寻找一个简单问题的解决方案。
例子:
SELECT date, date(date)
FROM date;
这是一个相当愚蠢的示例,其中一个 table、它的列和一个函数都具有名称 "date"。
我的语法片段(非常简化):
simple_select
: SELECT selected_element (',' selected_element) FROM from_element ';'
;
selected_element
: function
| REGULAR_WORD
;
function
: REGULAR_WORD '(' function_argument ')'
;
function_argument
: REGULAR_WORD
;
from_element
: REGULAR_WORD
;
DATE: D A T E;
FROM: F R O M;
SELECT: S E L E C T;
REGULAR_WORD
: (SIMPLE_LETTER) (SIMPLE_LETTER | '0'..'9')*
;
fragment SIMPLE_LETTER
: 'a'..'z'
| 'A'..'Z'
;
DATE 是一个关键字(它在语法的其他地方使用)。 如果我想让它被我的语法识别为一个普通词,这里是我的解决方案:
1) 我把它加到我用过的地方 REGULAR_WORD, 旁边。 示例:
selected_element
: function
| REGULAR_WORD
| DATE
;
=> 我不想要这个解决方案。我不仅有 "DATE" 作为关键字,而且我有很多使用 REGULAR_WORD 的规则,所以我需要添加许多(50+)关键字的列表,如 DATE 到许多(20+)解析器规则:这绝对是丑陋的。
优点:做一棵干净的树
缺点:使语法变脏
2) 我在两者之间使用解析器规则来获取所有这些关键字,然后,我用该解析器规则替换每个出现的 REGULAR_WORD 。 示例:
word
: REGULAR_WORD
| DATE
;
selected_element
: function
| word
;
=> 我也不想要这个解决方案,因为它在树中添加了一个解析器规则并污染了信息(我不想知道 "date" 是一个词,我想知道它是一个 selected_element、一个函数、一个 function_argument 或一个 from_element ...
优点:语法清晰
缺点:做一棵脏树
无论哪种方式,我都有一个脏树或脏语法。有没有办法让两者都干净?
我寻找了别名、等效的解析器片段,但 ANTLR4 似乎没有?
谢谢,祝你有个愉快的一天!
Antlr4 grammar repository SQL 方言有四种不同的语法,所有四种都使用您的第二种策略。所以似乎 Antlr4 sql 语法作者之间达成了共识。考虑到 Antlr4 词法分析器的设计,我认为没有更好的解决方案。
正如您所说,这会导致整个解析树中出现一些噪音,但相关的非终结符(function
、selected_element
等)肯定存在并且确实存在在我看来,将单元产生式从解析树中折叠出来并不难。
据我了解,在设计 Antlr4 时,决定只自动生成完整的解析树,因为压缩 ("abstract") 语法树的设计太特殊了,不适合语法数字用户线。所以如果你觉得 AST 更方便,你有责任自己生成一个。这通常是直截了当的,尽管它涉及很多样板文件。
其他解析器生成器确实有可以处理 "semireserved keywords" 的机制。特别是,作为 Sqlite 项目一部分的 Lemon 解析器生成器包含一个 %fallback
声明,该声明允许您指定一个或多个标记应在没有语法规则允许的上下文中自动重新分类用过的。不幸的是,Lemon 不会生成 Java 个解析器。
另一个类似的选择是使用支持 "scannerless" 解析的解析器生成器。此类解析器通常使用像 Earley/GLL/GLR 这样的算法,能够解析任意 CFG,以解决比 LALR(1) 等固定先行算法可以方便地支持更多先行的需求。
这就是所谓的关键字作为标识符的问题,之前已经讨论过很多次了。例如,我 6 年前就在 ANTLR mailing list. But also here at Whosebug there are questions touching this area, for instance Trying to use keywords as identifiers in ANTLR4; not working.
中问过一个类似的问题Terence Parr 在 2008 年写了一篇 wiki article for ANTLR3,其中简要描述了 2 种可能的解决方案:
This grammar allows "if if call call;" and "call if;".
grammar Pred;
prog: stat+ ;
stat: keyIF expr stat
| keyCALL ID ';'
| ';'
;
expr: ID
;
keyIF : {input.LT(1).getText().equals("if")}? ID ;
keyCALL : {input.LT(1).getText().equals("call")}? ID ;
ID : 'a'..'z'+ ;
WS : (' '|'\n')+ {$channel=HIDDEN;} ;
You can make those semantic predicates more efficient by intern'ing those strings so that you can do integer comparisons instead of string compares.
The other alternative is to do something like this
identifier : KEY1 | KEY2 | ... | ID ;
which is a set comparison and should be faster.
通常,正如@rici 已经提到的,人们更喜欢将所有关键字保留在自己的规则中并将其添加到普通标识符规则(允许此类关键字的地方)的解决方案。
wiki 中的其他解决方案可以针对任何关键字进行推广,方法是在 ID
词法分析器规则的操作中使用查找 table/list,该规则用于检查给定字符串是否是一个关键字。此解决方案不仅速度较慢,而且还牺牲了解析器语法的清晰度,因为您不能再在解析器规则中使用关键字标记。