我正在寻找一个简单问题的解决方案。
示例:
SELECT date, date(date)
FROM date;
这是一个相当愚蠢的例子,其中表、它的列和一个函数都有名称"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
;
=> 我也不想要这个解决方案,因为它在树中添加了一个解析器规则并污染了信息(我不想知道"日期"是一个词,我想知道它是一个selected_element、一个函数、一个function_argument或一个from_element......
优点:制定干净的语法
缺点: 做一棵肮脏的树
无论哪种方式,我都有一棵肮脏的树或肮脏的语法。难道没有办法让两者都干净吗?
我寻找别名,解析器片段等效,但似乎 ANTLR4 没有任何?
谢谢,祝你有美好的一天!
Antlr4 语法库中有四种不同的 SQL 方言语法,所有四种语法都使用您的第二种策略。因此,Antlr4 sql 语法编写者之间似乎达成了共识。鉴于Antlr4词法分析器的设计,我不相信有更好的解决方案。
正如你所说,这会导致完整的解析树中出现一些噪音,但相关的非终端(function
、selected_element
等(肯定存在,在我看来,将单元生产从解析树中折叠出来并不困难。
据我了解,在设计 Antlr4 时,决定只自动生成完整的解析树,因为压缩("抽象"(语法树的设计太特殊了,不适合语法 DSL。因此,如果您发现 AST 更方便,您有责任自己生成一个。这通常是直截了当的,尽管它涉及很多样板。
其他解析器生成器确实具有可以处理"半保留关键字"的机制。特别是,Lemon 解析器生成器是 Sqlite 项目的一部分,它包含一个%fallback
声明,它允许您指定在没有语法规则允许使用的上下文中应自动重新分类一个或多个标记。不幸的是,Lemon不生成Java解析器。
另一个类似的选择是使用支持"无扫描仪"解析的解析器生成器。此类解析器通常使用 Earley/GLL/GLR 等算法,能够解析任意 CFG,以解决比固定前瞻算法(如 LALR(1( (更方便支持的更多前瞻需求。
这就是所谓的关键字作为标识符问题,之前已经讨论过很多次了。例如,6年前,我在ANTLR邮件列表中已经问过类似的问题。但是在Stackoverflow中,也有一些问题涉及这一领域,例如尝试在ANTLR4中使用关键字作为标识符;不工作。
Terence Parr在2008年为ANTLR3写了一篇wiki文章,简要描述了2种可能的解决方案:
此语法允许"if if call call;"和"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;} ;
您可以通过实习这些字符串来提高这些语义谓词的效率,以便您可以进行整数比较而不是字符串比较。
另一种选择是做这样的事情
identifier : KEY1 | KEY2 | ... | ID ;
这是一个集合比较,应该更快。
通常,正如@rici已经提到的,人们更喜欢将所有关键字保留在自己的规则中并将其添加到常规标识符规则(允许此类关键字(的解决方案。
wiki 中的另一种解决方案可以推广到任何关键字,方法是在ID
词法分析器规则的操作中使用查找表/列表,该规则用于检查给定字符串是否为关键字。此解决方案不仅速度较慢,而且还会牺牲解析器语法的清晰度,因为您不能再在解析器规则中使用关键字标记。