当前位置: 首页 > 知识库问答 >
问题:

ANTLR lexer规则中的句法谓词

孙项禹
2023-03-14

查看文档,ANTLR2过去有一种叫做谓词法的东西,下面的例子是这样的(灵感来自Pascal):

RANGE_OR_INT
    :   ( INT ".." ) => INT  { $setType(INT); }
    |   ( INT '.' )  => REAL { $setType(REAL); }
    |   INT                  { $setType(INT); }
    ;    

在我看来,这实际上是规则开头的一个积极的前瞻性断言:如果前瞻性与int“..”匹配,那么第一个规则将被应用(并与该输入的int部分匹配),依此类推。

我还没有在ANTLR4中找到这样的东西。2到3迁移指南似乎没有提到这一点,而3到4更改文档指出:

ANTLR3和4之间最大的区别是ANTLR4接受您给出的任何语法,除非该语法具有间接的左递归。这意味着我们不需要语法谓词或回溯,所以ANTLR4不支持该语法;你会因为使用它而得到警告。

如果我保持原样,这与我得到的错误消息是一致的:

(...)=> syntactic predicates are not supported in ANTLR 4

虽然我可以理解一个更智能的解析器实现将如何解决这些歧义,但我不知道这将如何适用于lexer。

当然,让我们来试试这个:

grammar Demo;
prog:   atom (',' atom)* ;
atom:   INT  { System.out.println("INT:   " + $INT.getText()); }
    |   REAL { System.out.println("REAL:  " + $REAL.getText()); }
    |   a=INT RANGE b=INT { System.out.println("RANGE: " +
                              $a.getText() + " .. " + $b.getText()); }
    ;
WS  :   (' ' | '\t' | '\n' | '\r')+ -> skip ;
INT :   ('0'..'9')+ ;
REAL:   INT '.' INT? | '.' INT ;
RANGE:  '..' ;
$ wget -nc http://www.antlr.org/download/antlr-4.5.2-complete.jar
$ java -jar antlr-4.5.2-complete.jar Demo.g
$ javac -cp antlr-4.5.2-complete.jar Demo*.java
$ java -cp .:antlr-4.5.2-complete.jar org.antlr.v4.gui.TestRig \
  Demo prog <<< '1,2.,3.4,5 ..6,7..8'
INT:   1
REAL:  2.
REAL:  3.4
RANGE: 5 .. 6
REAL:  7.
line 1:17 extraneous input '.8' expecting {<EOF>, ','}

因此,我似乎是正确的:虽然删除语法先发制人可能适合解析器,但lexer不会突然猜出正确的令牌类型。

那么如何将这个具体的例子转换为ANTLR4呢?有没有一种表达前瞻性条件的方法?或者是让像int'..'这样的单个规则发出两个不同的标记的方法?

查看ANTLR4Pascal语法,我注意到它不允许实数以.结尾,后面没有数字,因此从那里学习解决方案似乎不是一个选项。

在lexer中检查previous/left token的答案提到了lexer的emit方法,并在注释中引用了如何在每个lexer规则中发出不止一个token?在ANTLR3维基中的FAQ页面,所以我想这是一种方法。我会把这个变成一个答案,如果没有人打败我,如果我能让它在我的例子中工作。

lexer中对ANTLR4负向前看的回答使用_input.la(int)方法来检查向前看。ANTLR4词法分析faq提到_input.la但没有详细说明。这也适用于上面的示例,但对于需要考虑的前瞻字符不止一个的场景,这将是困难的。

共有1个答案

岳华灿
2023-03-14

下面是一个非常简短的解决方案:

@lexer::members { private int _pos; }
INT_RANGE: INT  { _pos=_input.index(); setType(INT); emit(); }
           '..' { _input.seek(_pos); };

这与整个int'..'表达式相匹配,但随后将输入倒回到int之后,在这里发出标记并保存位置。然后在规则的末尾使用该位置以更持久的方式倒回输入。

但是,有一个问题:由于_input.seek不会影响getCharpositionInline返回的内容,因此生成的标记将具有不正确的位置信息。在这种情况下,人们可以做

setCharPositionInLine(getCharPositionInLine() - 2)

查看LexeratnSimulator.evaluatePredicate,我看到该方法努力还原给定的位置状态。因此,我们可以通过滥用语义谓词的副作用来获得正确的状态:

@lexer::members {
    private int _savedIndex, _savedLine, _savedColumn;
    private boolean remember() {
        _savedIndex = _input.index();
        _savedLine = getLine();
        _savedColumn = getCharPositionInLine();
        return true;
    }
    private void recall(int type) {
        _input.seek(_savedIndex);
        setLine(_savedLine);
        setCharPositionInLine(_savedColumn);
        setType(type);
    }
}
INT_RANGE: INT { remember() }? '..' { recall(INT); } ;

请记住,语义谓词将在一个还不能保证整个表达式实际上匹配的时间点执行。因此,如果在多个地方使用此技巧,则必须小心不要从不同规则获得覆盖状态的remement()调用。如果有疑问,可以使用多个这样的函数,或者在数组中使用索引,以使每个匹配都不含糊。

 类似资料:
  • 本文向大家介绍谓词演算的推理规则,包括了谓词演算的推理规则的使用技巧和注意事项,需要的朋友参考一下 为了从我们已经知道其真实性的陈述中推断出新的陈述,使用了推理规则。 推理规则有什么用? 数学逻辑通常用于逻辑证明。证明是确定数学陈述的真值的有效参数。 参数是一系列语句。最后一个陈述是结论,其前面的所有陈述都称为前提(或假设)。结论之前放置符号“∴”(因此请阅读)。一个有效的论点是根据前提的真实值得

  • SQL 语句的大小写规则与语句组成元素、引用内容和服务器所使用的操作系统有关。 1) SQL 关键字和函数名 SQL 的关键字和函数名不区分大小写。例如,下面这些语句都是等价的: SELECT NOW(); select now(); sElEcT nOw(); 2) 数据库名、表名和视图名 MySQL 用服务器主机的底层文件系统所包含的目录和文件来表示数据库和表。因此,数据库名和表名的默认大小写

  • 谁能告诉我如何用Hazelcast谓词“in”子句吗。我想使用以下内容, 我想将值作为值的ArrayList传递。但是它抛出错误,因为它期望实现可比的对象。对此有任何解决方法吗?

  • 主要内容:JSON 中的键,JSON 中的值,JSON 与 JavaScript 对象的区别JSON 的语法与 JavaScript 中的对象很像,在 JSON 中主要使用以下两种方式来表示数据: Object(对象):键/值对(名称/值)的集合,使用花括号定义。在每个键/值对中,以键开头,后跟一个冒号,最后是值。多个键/值对之间使用逗号分隔,例如; Array(数组):值的有序集合,使用方括号定义,数组中每个值之间使用逗号进行分隔。 下面展示了一个简单的 JSON 数据: 注意:所有