ANTLR词法规则中的句法谓词 介绍

ANTLR词法规则中的句法谓词 介绍,antlr,antlr4,regex-lookarounds,lexical-analysis,Antlr,Antlr4,Regex Lookarounds,Lexical Analysis,查看文档,ANTLR 2以前有一个名为的东西,有这样的例子(灵感来自Pascal): 在我看来,这本质上是规则开头的一个积极的前瞻性断言:如果前瞻性匹配INT.“则第一条规则将应用(并匹配该输入的INT部分),依此类推 我还没有在ANTLR 4中找到类似的东西。政府似乎没有提到这一点,而各州: ANTLR3和4之间最大的区别在于,ANTLR4接受您给出的任何语法,除非该语法具有间接左递归。这意味着我们不需要语法谓词或回溯,因此ANTLR 4不支持该语法;你使用它会得到警告 这与我将其保持原样时收

查看文档,ANTLR 2以前有一个名为的东西,有这样的例子(灵感来自Pascal):

在我看来,这本质上是规则开头的一个积极的前瞻性断言:如果前瞻性匹配
INT.“
则第一条规则将应用(并匹配该输入的
INT
部分),依此类推

我还没有在ANTLR 4中找到类似的东西。政府似乎没有提到这一点,而各州:

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

这与我将其保持原样时收到的错误消息一致:

(...)=> 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:  '..' ;
将其保存到
Demo.g
,然后编译并运行:

$wget-nchttp://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包含几个关于多个令牌发射的docstring条目。当然,这些在中也有体现。根据这些规定,必须做到以下几点:

  • 覆盖:

    默认情况下,不支持每次
    nextToken
    调用多次发射 为了提高效率。子类化并重写此方法,
    nextToken
    , 和
    getToken
    (将令牌推入列表并从该列表中提取) 而不是像此实现那样使用单个变量)

  • 覆盖

  • 覆盖:

    如果发出多个令牌,则重写

  • 确保设置为非空
  • null:

    如果您将子类化为允许多个令牌 排放,然后将其设置为要匹配的最后一个标记,或 非null的值,以便自动令牌发射机制不会 发射另一个令牌

    但是,我不明白为什么重写
    getToken
    会很重要,因为我在运行库的任何地方都没有看到对该方法的调用。如果您设置了
    \u token
    ,那么这也将是
    getToken
    的输出

    因此,我从一条规则中发出两个令牌的方法是:

    @lexer::members{
    专用令牌排队;
    @覆盖公共令牌nextToken(){
    如果(_排队!=null){
    发射(_排队);
    _排队=空;
    返回getToken();
    }
    返回super.nextToken();
    }
    @重写公共令牌emit(){
    如果(_类型!=整数范围)
    返回super.emit();
    令牌t=\u factory.create(
    _tokenFactorySourcePair,INT,null,_通道,
    _tokenStartCharIndex,getCharIndex()-3,
    _TokenStartCharpositionLine,_TokenStartCharpositionLine);
    _排队=\u factory.create(
    _tokenFactorySourcePair,范围,空,_通道,
    getCharIndex()-2,getCharIndex()-1,\u,
    _TokenStartCharpositionLine+getCharIndex()-2-
    _代币StartCharIndex);
    发射(t);
    返回t;
    }
    }
    INT_范围:INT'..';
    

    然而,所有的位置计算都感觉非常乏味,给了我另一个想法(至少对于这个应用程序来说要好得多),我将在一个简短的回答中发布这个想法。

    这里有一个非常简短的解决方案:

    @lexer::members { private int _pos; }
    INT_RANGE: INT  { _pos=_input.index(); setType(INT); emit(); }
               '..' { _input.seek(_pos); };
    
    这将匹配整个
    INT'..'
    表达式,但随后将输入倒回到
    INT
    之后,在那里我们发出标记并保存位置。然后在规则末尾使用该位置以更持久的方式倒带输入

    但是,存在一个问题:由于
    \u输入,生成的标记将具有不正确的位置信息。seek
    不会影响
    getCharPositionInLine
    返回的内容。在这种情况下,我们可以这样做

    setCharPositionInLine(getCharPositionInLine() - 2)
    
    在规则的末尾,但是如果不是
    ,而是处理可变长度的输入,则该方法将不起作用。我曾希望能够在第一个操作中保存
    getCharPositionInLine()
    的结果,但不幸的是,这已经反映了整个表达式的结束

    通过查看,我发现此方法努力恢复给定的位置状态。因此,我们可以通过滥用语义谓词的副作用来获得正确的状态:

    @lexer::members{
    私有整数保存索引、保存行、保存列;
    私有布尔记忆(){
    _savedIndex=_input.index();
    _savedLine=getLine();
    _savedColumn=getCharPositionInLine();
    返回true;
    }
    私有无效调用(int类型){
    _input.seek(_savedIndex);
    设置行(_savedLine);
    setCharPositionInLine(_savedColumn);
    setType(类型);
    }
    }
    INT_RANGE:INT{memory()}?'..'{回忆(INT);};
    
    请记住,语义谓词将在尚未保证整个表达式实际匹配的时间点执行。因此,如果您在多个地方使用此技巧,您必须小心不要让来自不同规则的调用覆盖状态。如果有疑问,您可以使用多个这样的函数,或者将索引放入数组中,以使每个匹配明确无误

    setCharPositionInLine(getCharPositionInLine() - 2)