最近工作经常跟antlr打交道,一开始半懂不懂,各种瞎写胡试,也能work。工作做完之后就想系统了解下编译器前端到底做了哪些事情,也就是词法分析和语法分析究竟做了什么,以及antlr的lexer和parser的语法。
编译器前端到底做了哪些事情
从一个比较high level的角度看待编译器前端对字符串的处理流程,第一步是做词法解析,将字符串解析成Token序列,第二步是做语法分析,根据语法匹配命中相应的语法规则。
词法分析
以这句SPL查询
search id |stats count by id为例,词法分析做的事情就是把这个字符串解析成Token序列:search、id、stats、count、by、|,同时也会拿到这些token对应的类型。语法分析
词法分析的结果作为语法分析器的输入,经过语法分析之后会确定这个SPL的AST语法树。
antlr语法
Lexer语法规则
(1)匹配规则
最长匹配
字符串可能会命中多个词法,这时候会选择最长命中词法。举例如下
输入:a=b
词法文件
EQ: '=';
IDENTIFIER:[a-zA-z]*;
ANY: .*; // 命中规则ANY这时候词法分析不会将
a=b理解成a、=、b,而是将他们看成一个整体顺序匹配
字符串命中多个词法,在字符串长度一致的情况下,会按照从上至下的顺序优先匹配最先出现的词法。举例如下
输入:a
语法文件
IDENTIFIER:[a-zA-z]*; // 命中规则IDENTIFIER
ANY: .*;这时候词法分析会优先命中最先出现的词法IDENTIFER
(2)语法关键字理解 antlr lexer 语法
skip
skip会告诉lexer丢弃掉当前匹配到的词,去读下一个词。比如我们在自定义语法的时候,必然会打很多空格,这些空格其实是没有什么含义的,不想对空格做特殊处理的话就可以把这些空格skip掉啦
WS: [ \t\r\n\u3000]+ -> skip;
more
more这个我很少用到,大概做的跟skip反过来,遇到more的时候就会告诉lexer再去读一个词
LQUOTE : '"' -> more, mode(STR);
mode STR;
STRING : '"' -> mode(DEFAULT_MODE) ; // token we want parser to see
TEXT : . -> more ; // collect more text for string当遇到
"时,我们告诉lexer还需要匹配更多词,同时通过mode进入STR模式mode(x)
通过mode建立一个新的命名空间,可以解决与其他模式的冲突,进入新的命名空间可以用其他命名空间定义的词法
WHERE: 'where'
mode(NEWSPACE);
NEWSPACE_WHERE: WHERE -> type(WHERE)这种写法在进入
NEWSPACE命名空间之后,就可以复用在其他命名空间定义的WHERE啦pushMode(x)
进入新的命名空间
CDATA : '<![CDATA[' .*? ']]>' ;OPEN : '<' -> pushMode(INSIDE);
mode INSIDE;
CLOSE : '>' -> popMode ;
SPECIAL_CLOSE: '?>' -> popMode ; // close <?xml...?>
SLASH_CLOSE : '/>' -> popMode ;这时候会进入新的命名空间
INSIDEpopMode(x)
退出当前命名空间,如果直接写
popMode,就会回到原来的命名空间,这时候可以理解pushMode是不断的压栈,栈顶元素是当前命名空间type(x)
解决的是不同命名空间下,可以使用同名Token的问题,参考mode(x)的说明
channel(x)
当我们使用定义的语法去写SPL的时候,我们是不会关心像注释、空格这样的东西,这个时候可以将注释、空格导流到不同的channel,源文件解析后的Token会进入默认的channle。
@lexer::members{
public static final int WHITESPACE = 1;
public static final int COMMENTS = 2;
}
WS: [ \t\r\n\u3000]+ -> channel(WHITESPACE);
COMMENT: '//' .*? '\n' -> channel(COMMENTS);
语法解析规则
其实parser这边没什么好讲的,parser就是根据Token序列判断是哪个语法。举个整体的例子吧
词法文件
lexer grammar Splv1Lexer;
SORT: 'sort';
TOP: 'top';
BY: 'by';
IDENTIFIER: [a-zA-Z0-9]*语法文件
lexer grammar Splv1Parser;
options { tokenVocab=Splv1Lexer; }
main
: operators EOF
;
operators:
: operator*
;
operator:
: SORT BY field #sort
| TOP field # top
;
field: IDENTIFIER;这个时候我们输入
sort by abc,词法分析会将其分析为词:SORT、BY、IDENTIFIER,之后在语法分析阶段会命中语法SORT BY field #sort
antlr with java
这部分TODO吧,哈哈哈主要是我也没有搭过,但是简单看了下就是利用antlr自动生成的文件折腾折腾就可以啦
如下图所示
