【问题标题】:How Lexer lookahead works with greedy and non-greedy matching in ANTLR3 and ANTLR4?Lexer lookahead 如何处理 ANTLR3 和 ANTLR4 中的贪婪和非贪婪匹配?
【发布时间】:2014-01-31 06:25:22
【问题描述】:

如果有人能让我从前瞻关系与涉及贪婪/非贪婪匹配的标记化背后的困惑中解脱出来,我会非常高兴。请注意,这是一篇稍长的帖子,因为它遵循了我的思考过程。

我正在尝试编写允许我匹配输入的 antlr3 语法,例如:

“标识符关键字”

我在 Antlr 3.4 中想出了一个这样的语法:

KEYWORD: 'keyword' ;

IDENTIFIER
: 
  (options {greedy=false;}: (LOWCHAR|HIGHCHAR))+ 
;

/** lowercase letters */
fragment LOWCHAR
:   'a'..'z';
/** uppercase letters */
fragment HIGHCHAR
:   'A'..'Z';

parse: IDENTIFIER KEYWORD EOF;

但是它抱怨它永远无法以这种方式匹配 IDENTIFIER,我真的不明白。 (以下选项永远无法匹配:1)

基本上,我试图为尝试匹配 (LOWCHAR|HIGHCHAR) 非贪婪方式的词法分析器指定,因此它在 KEYWORD 前瞻处停止。到目前为止,我所读到的关于 ANTLR 词法分析器的内容应该是词法分析器规则的某种优先级。如果我在词法分析器语法中首先指定 KEYWORD 词法分析器规则,那么后面的任何词法分析器规则都不能匹配所使用的字符。

经过一番搜索,我了解到这里的问题是它无法以正确的方式对输入进行标记,因为例如对于输入:“identifierkeyword”,“标识符”部分首先出现,因此它决定在那里开始匹配 IDENTIFIER 规则还没有 KEYWORD 标记匹配。

然后我尝试在 ANTLR 4 中编写相同的语法,以测试新的 run-ahead 功能是否可以匹配我想要的,它看起来像这样:

KEYWORD: 'keyword' ;

/** lowercase letters */
fragment LOWCHAR
:   'a'..'z';
/** uppercase letters */
fragment HIGHCHAR
:   'A'..'Z';

IDENTIFIER
: 
  (LOWCHAR|HIGHCHAR)+?
;

parse: IDENTIFIER KEYWORD EOF;

对于输入:“identifierkeyword”,它会产生以下错误: 第 1:1 行不匹配的输入“d”需要“关键字”

它将字符“i”(第一个字符)作为 IDENTIFIER 标记匹配,然后解析器期望得到一个他不会以这种方式获得的 KEYWORD 标记。

词法分析器的非贪婪匹配是否应该匹配,直到未来有任何其他可能性可用?难道它不应该提前考虑 IDENTIFIER 可以包含 KEYWORD 并以这种方式匹配它的可能性吗?

我对此感到非常困惑,我观看了 Terence Parr 介绍 ANTLR4 的新功能的视频,其中他谈到了在实际匹配规则的同时一直监视所有“正确”解决方案的预运行线程。我认为它也适用于 Lexer 规则,其中对输入“identifierkeyword”进行标记的可能正确解决方案是匹配 IDENTIFIER:“identifier”和匹配 KEYWORD:“keyword”

我认为我对非贪婪/贪婪匹配有很多错误。有人可以解释一下它是如何工作的吗?

毕竟我在这里找到了一个类似的问题:ANTLR trying to match token within longer token 并制作了一个与之对应的语法:

parse
:   
  identifier 'keyword'
;

identifier
:   
  (HIGHCHAR | LOWCHAR)+
;

/** lowercase letters */
LOWCHAR
:   'a'..'z';
/** uppercase letters */
HIGHCHAR
:   'A'..'Z';

这就是我现在想要的,但是我不明白为什么我不能将标识符规则更改为 Lexer 规则,将 LOWCHAR 和 HIGHCHAR 更改为片段。 词法分析器不知道“关键字”中的字母可以作为标识符匹配吗?或相反亦然?或者可能是规则仅被定义为在自身内部具有前瞻功能,而不是所有可能的匹配语法?

【问题讨论】:

    标签: parsing antlr antlr3 antlr4 lexer


    【解决方案1】:

    在 ANTLR 3 和 ANTLR 4 中解决此问题的最简单方法是仅允许 IDENTIFIER 匹配单个输入字符,然后创建解析器规则来处理这些字符的序列。

    identifier : IDENTIFIER+;
    IDENTIFIER : HIGHCHAR | LOWCHAR;
    

    这将导致词法分析器将输入 identifier 作为 10 个单独的字符跳过,然后将 keyword 作为单个 KEYWORD 标记读取。

    您在 ANTLR 4 中使用非贪婪运算符 +? 观察到的行为与此类似。该运算符表示“尽可能少匹配(HIGHCHAR|LOWCHAR) 块,同时仍创建IDENTIFIER 令牌”。显然,创建令牌的最少数字是 1,因此这实际上是编写 IDENTIFIER 以匹配单个字符的一种非常低效的方式。 parse 规则未能处理此问题的原因是它只允许单个 IDENTIFIER 令牌出现在 KEYWORD 令牌之前。通过创建解析器规则 identifier,就像我在上面展示的那样,解析器将能够将 IDENTIFIER 标记的序列(每个都是单个字符)视为单个标识符。

    编辑:您在 ANTLR 3 中收到消息“以下替代方案永远无法匹配...”的原因是静态分析已确定规则 IDENTIFIER 中的正闭包将永远不要匹配更多超过 1 个字符,因为规则将始终成功 恰好 1 个字符。

    【讨论】:

    • 啊,这就是非贪婪匹配的意义所在,它匹配可能的最小标记。我认为它匹配最长可能的 IDENTIFIER 令牌,同时在前瞻中寻找冲突的词法分析器规则,然后按优先级决定。谢谢你的回答,如果你不介意我会留下这个问题,因为我仍然有部分我不明白为什么我会收到这个错误:“(以下替代方案永远无法匹配:1)”我的第一个 antlr3 语法。
    • 那太棒了,那么全功劳归于你 :) 非常感谢你清除了我脑海中的所有乌云。其实我从来不知道我会这么高兴学习新东西
    • 请问您是否也愿意在这件事上帮助我? (这个问题已经出现了很多,我也增加了一个赏金):stackoverflow.com/questions/20938550/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    • 2011-08-29
    • 1970-01-01
    • 1970-01-01
    • 2017-10-16
    • 2015-02-11
    • 1970-01-01
    相关资源
    最近更新 更多