Coup de Grace

Antlr与模板引擎

Antlr的功效是帮助我们实现解释器来理解dsl或者模板引擎文件.

事出原因也是算还愿吧,很久以前有人问我这方面的东西没有答好.

基础

官方的grammar模板

描述其他数据的数据就是元数据,一如我们编写的g4文件.

我们使用idea上的antlr插件来调试g4文件/生成Lexer与Parser/生成Visitor与Listener来进行树的遍历.

比如最简单的支持Velocity形式的变量访问:

那么g4文件:

grammar Tt;

NEWLINE:'\r'?'\n';
NUMBER: '0'..'9';
CHAR: 'a'..'z'|'A'..'Z';
UNDERLINE: '_';
SPACE:' ';
DOT:'.';
V_START:'${';
V_END:'}';

parse
	:expression*
	;
	
expression
	: stringtext
	| simpleVariable
	| complexVariable
	;

newline
	:NEWLINE NEWLINE*
	;

stringtext
	: placeholder (placeholder)*
	| newline
	;

placeholder
	: CHAR
	| ':'
	| SPACE
	| NUMBER
	| DOT
	| '\''
	| '"'
	| '<'
	| '>'
	| '_'
	| '+'
	| '-'
	| '*'
	| '/'
	;

simpleVariable
	:V_START simpleVariableInner V_END
	;

complexVariable
	:V_START complexVariableInner V_END
	;

simpleVariableInner
	:identity
	;

complexVariableInner
	:identity DOT identity
	;

identity
	:(UNDERLINE|CHAR) (UNDERLINE|CHAR|NUMBER)*
	;

在编辑器内parse行内右键Test Rule Parse

得到解析树,可以看到三行表达式被正确的解析为表达式+换行符.定义的符号也得到了正确的解析.

那么我们进行解析树的访问.

右键Generate ANTLR Recognizer,在同一package的gen文件夹内生成Lexer,Parser,Listener,Visitor.

引入Runtime后实现生成的Visitor接口即可,里面包含着所有表达式的访问入口要你来实现.

<dependency>
    <groupId>org.antlr</groupId>
    <artifactId>antlr4-runtime</artifactId>
    <version>4.5.3</version>
</dependency>

思路大都是维护一个context保存着所有变量,走到哪一步塞东西进去,最后传出渲染.

好了好了,到这步先太监了.

后续还会补,我找了个开源项目翻翻源码看看人家怎么继续实现模板引擎所有功能和接入Spring的.

好的,我们受到这篇的感召回来把进行补完.

模板引擎实现

REF

接上回书我们这次看些Lexer与Parser实例

其中Lexer都是正则匹配某些字符串解释为token,Parser得到tokens之间的关系.最后Generate ANTLR Recognizer.

我们看到如下Lexer将文字/控制结构等等进行匹配

//fragment IF_ARG_START : [ \t]+ 'if'    ARG_START         ;
fragment NAME_ARG_START : [ \t]+ ID      ARG_START         ;
fragment ARG_START      : [ \t]* '('                       ;
fragment EMPTY_ARG      : '()'                             ;
fragment ID             : [_a-zA-Z][_a-zA-Z0-9]*           ;


// Following is invalid directives
DIRECTIVE_OPTIONS       : '#options'                       ;
DIRECTIVE_DEFINE        : '#define'                        ;
DIRECTIVE_SET           : '#set'                           ;
DIRECTIVE_IF            : '#if'                            ;
DIRECTIVE_ELSEIF        : '#elseif'                        ;
DIRECTIVE_FOR           : '#for'                           ;
DIRECTIVE_INCLUDE       : '#include'                       ;
DIRECTIVE_TAG           : '#tag'                           ;
DIRECTIVE_CALL          : '#call'                          ;
DIRECTIVE_MACRO         : '#macro'                         ;


// It is a text which like a directive.
// It must be put after directive definition to avoid conflict.
TEXT_DIRECTIVE_LIKE     : '#' [a-zA-Z0-9]+                 ;

如下的Parser将block描述为任意次数的text/value/directive叠在一起.

template    :   block EOF
            ;

block       :   (text | value | directive)*
            ;

text        :   TEXT_PLAIN
            |   TEXT_CDATA
            |   TEXT_CHAR_SINGLE
            |   TEXT_CHAR_ESCAPED
            |   TEXT_DIRECTIVE_LIKE
            ;

value       :   VALUE_OPEN         expression '}'
            |   VALUE_OPEN_ESCAPED expression '}'
            ;

directive   :   directive_options
            |   directive_define
            |   directive_set
            |   directive_if
            |   directive_for
            |   directive_break
            |   directive_continue
            |   directive_stop
            |   directive_include
            |   directive_return
            |   directive_tag
            |   directive_call
            |   directive_macro
            |   directive_invalid
            ;

语法解析

load模板文件到内存,转为字符数组传入ANTLRInputStream

TtTemplateParser parser = new TtTemplateParser(new CommonTokenStream(new TtTemplateLexer(is)));
TtTemplateParser.TemplateContext context = parser.template();

得到TemplateContext后,在解析树中叶子节点与分支节点有不同的待遇.使用Operator对两种节点进行处理.

Operator的逻辑是什么呢?

渲染

渲染就把解析后的串写入OutputStream就好了.

etc

细节都说完,那么数据的出入口都在哪里呢?

Servlet或者其他协议实现.

当然了,目前的实现Layout和Macro功能都还没实现.

不过就先这样吧,第二次太监,以后有机会的话再补上.

done.