MvG*_*MvG 5 antlr lexical-analysis antlr4 regex-lookarounds
查看文档,ANTLR 2曾经有一个称为谓词词法的东西,例如这样的例子(受Pascal启发):
RANGE_OR_INT
: ( INT ".." ) => INT { $setType(INT); }
| ( INT '.' ) => REAL { $setType(REAL); }
| INT { $setType(INT); }
;
Run Code Online (Sandbox Code Playgroud)
我的看法是,在规则开始处本质上是一个肯定的前瞻性断言:如果前瞻性匹配INT "..",则将应用第一个规则(并匹配该INT输入的部分),依此类推。
我尚未在ANTLR 4中找到类似的东西。在2至3的迁移指南似乎不同时提此,3〜4的变化记录状态:
ANTLR 3和4之间的最大区别是ANTLR 4可以采用您提供的任何语法,除非该语法具有间接左递归。这意味着我们不需要语法谓词或回溯,因此ANTLR 4不支持该语法。您会收到使用警告。
如果我基本上保持原样,这与我收到的错误消息是一致的:
(...)=> syntactic predicates are not supported in ANTLR 4
Run Code Online (Sandbox Code Playgroud)
虽然我可以理解更智能的解析器实现将如何解决这些歧义,但是我看不到这对词法分析器将如何工作。
可以肯定的是,让我们尝试一下:
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: '..' ;
Run Code Online (Sandbox Code Playgroud)
将其保存到Demo.g,然后编译并运行:
RANGE_OR_INT
: ( INT ".." ) => INT { $setType(INT); }
| ( INT '.' ) => REAL { $setType(REAL); }
| INT { $setType(INT); }
;
Run Code Online (Sandbox Code Playgroud)
因此,看来我是正确的:虽然语法分析谓词的删除可能适用于解析器,但词法分析器不会突然猜测正确的标记类型。
那么如何将这一特定示例转换为ANTLR 4?有没有一种表达提前条件的方法?还是有办法让一个规则像INT '..'发出两个不同的标记?
查看ANTLR 4 Pascal语法时,我注意到它不允许以数字结尾的实数以数字结尾.,因此从那里学习解决方案似乎没有选择。
我在ANTLR4中看到过语义谓词吗?和句法谓词-从Antlr 3升级到Antlr 4。两者都讨论解析器规则中的语法谓词。后者也有一个使用词法分析器规则的示例,但是前瞻与遵循该规则的规则相同,这意味着可以删除规则而不会产生不利影响。在上面的示例中,情况并非如此。
检查词法分析器中上一个/左标记的答案提到了词法分析器的emit方法,并附带注释:“ 如何根据每个词法分析器规则发射多个标记?ANTLR 3 Wiki中的FAQ页面,所以我想这是一种方法。如果没有人击败我,并且在我的示例中可以使它起作用,我将把它变成答案。
在lexer中对ANTLR4否定前瞻的答案使用该_input.LA(int)方法来检查前瞻。ANTLR 4 词法分析常见问题未提及_input.LA而提及。这也适用于上面的示例,但是对于要考虑的不只是单个预告字符的情况将很困难。
当前(截至撰写本文时)Lexer实现的源代码包含多个有关多个令牌发射的文档字符串条目。当然,这些也体现在API LexerJavaDoc中。根据这些,人们必须做以下事情:
覆盖emit(Token):
nextToken出于效率原因,默认情况下不支持每次调用多次发出。子类化并重写此方法、nextToken和getToken(将标记推入列表并从该列表中提取,而不是像此实现那样使用单个变量)。
覆盖nextToken()。
覆盖getToken():
如果发出多个令牌则覆盖。
确保设置_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 '..' ;
Run Code Online (Sandbox Code Playgroud)
然而,所有的位置计算都感觉非常乏味,并给了我另一个(至少对于这个应用程序来说更好)的想法,我将在一个单独的答案中发布。