【问题标题】:ANTLR4 : clean grammar and tree with keywords (aliases ?)ANTLR4:使用关键字(别名?)清理语法和树
【发布时间】:2020-01-09 02:57:41
【问题描述】:

我正在寻找一个简单问题的解决方案。

示例:

SELECT date, date(date)
FROM date;

这是一个相当愚蠢的示例,其中一个表、它的列和一个函数都具有名称“日期”。

我的语法的sn-p(非常简化):

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+) 关键字的列表添加到许多 (20+) 解析器规则中:绝对丑陋。

优点:制作一棵干净的树

缺点:弄脏语法

2) 我在两者之间使用解析器规则来获取所有这些关键字,然后用该解析器规则替换每次出现的 REGULAR_WORD。 示例:

word
    : REGULAR_WORD
    | DATE
    ;

selected_element
    : function
    | word
    ;

=> 我也不想要这个解决方案,因为它在树中添加了一个解析器规则并污染了信息(我不想知道“日期”是一个词,我想知道它是一个 selected_element 、函数、function_argument 或 from_element ...

优点:语法清晰

缺点:弄脏树

无论哪种方式,我都有一棵脏树或脏语法。没有办法让两者都干净吗?

我寻找别名,解析器片段等效,但似乎 ANTLR4 没有任何?

谢谢你,祝你有美好的一天!

【问题讨论】:

  • 恐怕这个问题没有解决办法。它被称为“上下文敏感词法分析器”。当您想要向后兼容的语法时,这是您必须付出的代价(例如 Oracle SQL - 它们确实区分“关键字”和“保留词”)
  • @ibre5041:谢谢你的回答。这就是我所害怕的。但是,“它被称为“上下文敏感词法分析器””是什么意思?你的意思是解决我的问题的语法会有一个“上下文敏感词法分析器”吗?

标签: antlr antlr4 grammar


【解决方案1】:

Antlr4 grammar repository 中有四种不同的 SQL 方言语法,它们都使用您的第二种策略。因此,Antlr4 sql 语法编写者之间似乎达成了共识。鉴于 Antlr4 词法分析器的设计,我认为没有更好的解决方案。

正如你所说,这会导致完整解析树中出现一些噪音,但相关的非终端(functionselected_element 等)肯定存在,而且在我看来并不存在很难将单位产生式从解析树中折叠出来。

据我了解,在设计 Antlr4 时,决定只自动生成完整的解析树,因为压缩(“抽象”)语法树的设计过于特殊,无法适应语法 DSL。因此,如果您发现 AST 更方便,您有责任自己生成一个。这通常是直截了当的,尽管它涉及很多样板。

其他解析器生成器确实具有可以处理“半保留关键字”的机制。特别是,作为 Sqlite 项目的一部分的 Lemon 解析器生成器包括一个 %fallback 声明,它允许您指定一个或多个标记应在没有语法规则允许使用它们的上下文中自动重新分类。不幸的是,Lemon 不生成 Java 解析器。

另一个类似的选择是使用支持“无扫描”解析的解析器生成器。此类解析器通常使用 Earley/GLL/GLR 等能够解析任意 CFG 的算法来解决比 LALR(1) 等固定前瞻算法方便支持的更多前瞻需求。

【讨论】:

  • 感谢您的完整回答!在解析后处理中折叠关键字确实不难,但如果您必须再次梳理树以删除“无用”节点,则它有点违背解析的目的。我想我现在有了答案,很遗憾 ANTLR 没有解决这个问题。看到其他解析器生成器想出了解决这个问题的方法是非常有趣的。干杯。
  • @louis:你不一定要修剪解析树;你可以简单地按照你想要的方式构建它。例如,这在基于 yacc/bison/lemon 的解析器中很常见。在大多数单元生产的情况下(a: b),AST 节点只是通过语义动作而不是构建一个新节点。传递是默认的 yacc 操作,但构建 AST 节点的操作是样板文件。
【解决方案2】:

这就是所谓的keywords-as-identifiers问题,之前已经讨论过很多次了。例如,我在 6 年前就在 ANTLR mailing list 中提出了类似的问题。但在 Stackoverflow 上也有涉及该领域的问题,例如 Trying to use keywords as identifiers in ANTLR4; not working

Terence Parr 在 2008 年写了一封 wiki article for ANTLR3,简要描述了两种可能的解决方案:

这个语法允许“if if 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 词法分析器规则中的操作中使用查找表/列表,该规则用于检查给定字符串是否为关键字。此解决方案不仅速度较慢,而且会牺牲解析器语法的清晰度,因为您不能再在解析器规则中使用关键字标记。

【讨论】:

  • 您好,感谢您的回答。确实还有其他相关问题,但没有一个给出令人满意的解决方案。现在我明白为什么了,因为似乎没有。我将@rici 的答案标记为问题的“解决方案”,因为它非常完整并提供了替代方案。您的回答确实给出了另一种解决方案,“{input.LT(1).getText().equals("if")}?”,但在我看来,它使语法依赖于语言,也使它变得更加晦涩难懂。谢谢你的回答,干杯。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多