场景介绍:
定义词法文件:
lexer grammar TestLexer;
options {
caseInsensitive = true;
}
// Lexer Rules
AGE : 'AGE' ;
PAREN_LEFT : '(';
PAREN_RIGHT : ')';
REGULAR_ID: SIMPLE_LETTER (SIMPLE_LETTER | '$' | '_' | '#' | [0-9])* ;
fragment SIMPLE_LETTER : [\p{Letter}\p{Emoji}];
SPACES: [ \n\t\r]+ -> channel(HIDDEN);
定义语法文件:
parser grammar TestParser;
options {
tokenVocab = TestLexer;
}
// Parser Rules
prog: expression EOF;
expression
: age_func
| regular_id
;
age_func
: AGE PAREN_LEFT PAREN_RIGHT
;
regular_id
: REGULAR_ID // 匹配普通标识符
;
antlr词法解析器定义了两个token
- AGE
- REGULAR_ID
按照antlr词法分析的规则,当一个字符串匹配的token有两个的时候,优先匹配第一个定义的token,所以当age出现的时候永远是匹配AGE,不会匹配REGULAR_ID
针对语法文件:
- 当用户输入age()的时候匹配:age_func语法(所以必须匹配AGE字符)
- 当用户输入age的时候匹配:regular_id语法(必须匹配REGULAR_ID字符)
所以需要再进行词法分析的时候动态的改变优先级,这是时候就需要用到谓词断言。
谓词断言:
在语法解析或者词法分析过程中插入一部分用户自定义代码,这段代码的返回值值bool
格式如下:
// 词法分析谓词断言,词法分析针对的是字符,所以是从满足token的最后一个下标开始分析
// 所以词法分析的谓词断言在后
AGE: 'AGE' {user_code()}?
// 语法分析谓词断言,语法分析针对的是上下文,只必须从第一个token开始分析断言
// 所以语法分析的谓词断言在前
expression
: {user_code()}? age_func
| regular_id
;
详细文档见:antlr中对于谓词断言的文档。
所以我们需要当词法分析达到AGE的末尾的时候根据后序字符的来判断是匹配AGE这个token,可以增加nextTokenIsLeftParen函数来进行后续字符的判断(可根据不同的target语言进行修改代码)。
修改后的词法分析的代码如下
lexer grammar TestLexer;
options {
caseInsensitive = true;
}
@lexer::members{
boolean nextTokenIsLeftParen() {
// 记录当前扫描到的字符下标
int index = _input.index()-1;
boolean res = false;
for (int i = 1;true;i++) {
char c = (char) _input.LA(i);
// 如果碰到我们定义的空白字符则继续扫描下一个
if (c == '\t' || c == '\n' || c == '\t' || c == ' ') {
continue;
}
// 如果碰到左括号,表示应该匹配AGE这个TOKEN
if (c == '(') {
res = true;
break;
}
// 如果下一个合法字符不是'('则跳出循环,这个时候结果是false
// 即不满足谓词断言,不匹配AGE
break;
}
// 因为LA消费了后序字符,所以我们要把指针调回AGE的末尾
_input.seek(index);
return res;
}
}
// Lexer Rules
AGE : 'AGE' {nextTokenIsLeftParen()}?;
PAREN_LEFT : '(';
PAREN_RIGHT : ')';
REGULAR_ID: SIMPLE_LETTER (SIMPLE_LETTER | '$' | '_' | '#' | [0-9])* ;
fragment SIMPLE_LETTER : [\p{Letter}\p{Emoji}];
SPACES: [ \n\t\r]+ -> channel(HIDDEN);
测试代码如下:
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;
import java.util.List;
public class Main {
public static void main(String[] args) {
CodePointCharStream age = CharStreams.fromString("age()");
TestLexer testLexer = new TestLexer(age);
CommonTokenStream commonTokenStream = new CommonTokenStream(testLexer);
commonTokenStream.fill();
// 打印token
List<Token> tokens = commonTokenStream.getTokens();
for (Token token : tokens) {
System.out.println(token);
}
TestParser testParser = new TestParser(commonTokenStream);
TestParser.ProgContext prog = testParser.prog();
// 打印语法树
String stringTree = prog.toStringTree(testParser);
System.out.println(stringTree);
}
}