2016-03-01 76 views
2

介绍

在文档来看,ANTLR 2配合使用,有一些所谓predicated lexing,结合实例像这样的(由帕斯卡启发):句法断言规则

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

我看到的样子它在本规则开始时基本上是一个积极的预见性断言:如果前瞻性匹配INT ".."那么第一个规则将应用(并匹配该输入的INT部分),依此类推。

我还没有在ANTLR 4中找到过这样的东西。该2 to 3 migration guide似乎并没有提到这一点,而3 to 4 changes document状态:

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

这是与错误信息,我得到行,如果我离开这个基本的是:

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

虽然我能理解一个更智能解析器实施将如何解决这些模糊之处,我失败看看这将如何工作词法分析器

再现例如

可以肯定,我们尝试了这一点:

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 -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>, ','} 

这样看来我是正确的:虽然删除语法预定可能适用于解析器,但词法分析器不会突然猜出正确的标记类型。

核心问题

那么一个会如何转换这个具体的例子来ANTLR 4?有没有办法表达先行条件?或者也许有一个像INT '..'这样的单个规则发出两个不同的标记的方法?

参考和可能的解决方案

望着ANTLR 4 Pascal grammar,我注意到,它不允许实数在.没有结束之后的数字,所以学习的解决方案从那里不会出现是一个选择。我看过Semantic predicates in ANTLR4?syntactic predicates - Upgrading from Antlr 3 to Antlr 4。在解析器规则中都讨论句法谓词。后者也有词法规则的例子,但前瞻与后面的规则是一致的,这意味着规则可以被移除而没有不利影响。在我上面的例子中,情况并非如此。

答案check previous/left token in lexer提词法分析器的emit方法,带有注释的ANTLR 3维基引用How can I emit more than a single token per lexer rule? FAQ页面,所以我想这是一种方法。如果没有人打我的话,我会把它变成一个答案,如果我能在我的例子中得到它的工作。

ANTLR4 negative lookahead in lexer的回答利用_input.LA(int)方法来检查前瞻。 ANTLR 4 lexical analysis faq提到_input.LA没有进入细节。这也适用于上面的例子,但对于不止一个字符的前瞻性考虑的场景来说很难。

回答

1

sources of the current (as of this writing) Lexer implementation包含多个关于多个标记发射的文档字符串条目。这些当然也代表the Lexer API JavaDoc。根据这些,就必须做到以下几点:

  1. 覆盖emit(Token)

    默认情况下不支持每个nextToken调用 多次发出出于效率的考虑。子类并覆盖此方法,nextToken, 和getToken(将令牌插入列表并从该列表中取出 而不是像本实现那样使用单个变量)。

  2. 覆盖nextToken()

  3. 覆盖getToken()

    覆盖如果发光多个令牌。

  4. 务必将_token到非null

    如果你继承允许多个令牌 排放,然后将其设置为最后一个令牌匹配或 非空的东西,这样的自动令牌发射机制不会 发出另一个令牌。

不过,我不明白为什么压倒一切的getToken将是重要的,因为我在运行时库看到没有调用该方法的任何地方。如果你设置了_token,那么这也将是getToken的输出。

所以我做了什么,从一个单一的规则发出两个令牌是这样的:

@lexer::members { 

    private Token _queued; 

    @Override public Token nextToken() { 
     if (_queued != null) { 
      emit(_queued); 
      _queued = null; 
      return getToken(); 
     } 
     return super.nextToken(); 
    } 

    @Override public Token emit() { 
     if (_type != INT_RANGE) 
      return super.emit(); 
     Token t = _factory.create(
      _tokenFactorySourcePair, INT, null, _channel, 
      _tokenStartCharIndex, getCharIndex()-3, 
      _tokenStartLine, _tokenStartCharPositionInLine); 
     _queued = _factory.create(
      _tokenFactorySourcePair, RANGE, null, _channel, 
      getCharIndex()-2, getCharIndex()-1, _tokenStartLine, 
      _tokenStartCharPositionInLine + getCharIndex()-2 - 
      _tokenStartCharIndex); 
     emit(t); 
     return t; 
    } 
} 

INT_RANGE: INT '..' ; 

所有位置计算感到很乏味,但是,并给了我另一个(至少对于这个应用程序好得多)我会在一个特别的答案中发布的想法。

1

这里是一个很短的解决方案:

@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) 

,但如果不是..一个正在处理可变长度的输入这种做法是行不通的。我本来希望能够在第一个动作中保存getCharPositionInLine()的结果,但不幸的是,这已经反映了整个表达的结束。

看着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); } ; 

记住,语义谓词会在某个时间点的地方,尚不能保证整个得到执行表达式实际上会匹配。所以如果你在几个地方使用这个技巧,你必须小心,你不会从覆盖状态的不同规则中调用remember()。如果有疑问,你可以使用多个这样的函数,或者一个索引到数组中,以使每个匹配都是明确的。