一、学习目的
项目研发过程中经常会需要将业务逻辑外置,需要将业务逻辑和代码分离。一般面对这样的需求有以下几种解决办法:
引入一个规则引擎,比如Drools。
利用java的javax.script.ScriptEngineManager调用javascript脚本。
利用antlr这样的开源项目定义自己的业务领域语言。
二、词法分析
把程序中的词法单元分为四类:标识符(分为关键字和一般标识符)、数字、特殊字符、空白(空格、Tab、回车换行等)
三、参考sql语句的词法分析和语法分析
Druid 的代码里,代表语法分析和词法分析的类分别是 SQLParser 和 Lexer。并且, Parser 拥有一个 Lexer。
public classSQLParser {protected finalLexer lexer;protectedString dbType;publicSQLParser(String sql, String dbType){this(newLexer(sql), dbType);this.lexer.nextToken();
}publicSQLParser(String sql){this(sql, null);
}publicSQLParser(Lexer lexer){this(lexer, null);
}publicSQLParser(Lexer lexer, String dbType){this.lexer =lexer;this.dbType =dbType;
}
}
View Code
Lexer 作为词法分析器,必然拥有其词汇表,在Lexer里,以 Keywords 表示。
Keywords 实际上是 key 为单词,value 为 Token 的字典型结构,其中 Token 是单词的类型,比如说,“select” 的 Token 类型就是 Select Token,而 “abc” 的 Token 类型,则是标识符,也表示为 Identifier Token。
而 MySqlLexer 类,除了沿用其父类的 Keywords 外,自己还有自己的 Keywords。可以理解为 Lexer 所维护的关键字集合,是通用的;而 MySqlLexer 除了有通用的关键字集合,也有属于 MySQL 数据库 SQL 方言的关键字集合。
Parser 是 Lexer 的使用者,站在 Parser 的角度看,它会怎么去使用 Lexer,或者说,Lexer 应该具备怎样的功能,才能满足 Parser 的使用需求。
Lexer 应该具备一个函数,能让使用者命令它解析一个单词,并且 Lexer 还必须提供一个函数,供使用者获取 Lexer 上一次解析到的单词以及单词的类型。
在 Lexer 中,nextToken() 这个方法提供了第一个需求,只要被调用,它就按顺序从 SQL 语句的开头到结尾,解析出下一个单词;token() 方法,则返回了上一次解析的单词的 Token 类型,如果 Token 类型是标识符(Identifier),Lexer 还提供了一个 stringVal() 方法,让使用者能拿到标识符的值。
走进 Lexer 的 nextToken() 方法,可以发现它的代码充斥着 if 语句和 switch 语句,因为解析单词的时候,是一个字符一个字符地解析,这就意味着,这个方法每次扫描一个字符,都必须判断单词是否结束,应该用什么方式来验证这个单词等等。这个过程,就是一个状态机运作的过程,每解析到一个字符,都要判断当前的状态,以决定应该进入下一个什么状态。
四、自定义动态语言处理框架
1、抽象的对象及作用
词法分析器
=>提供词汇表
=>提供语言解析
语法分析器
=>提供语法解析
=>构建语法树
语法树
=>一条自然语言构造成的语法树
语法树访问器
=>对语法树进行访问
2、各个角色之间的关系的伪代码
词法分析器
packagecom.spring.sxf.study.springtradedao.mysql.language;/*** 词法分析器
* 主要功能
* 1、提供词汇表
* 2、访问语句,提炼出词汇*/
public interfaceLexer {/*** 获取当前token
*@return
*/Token token();/*** 寻找下一个token*/
voidnextToken();/*** 获取被解析的语句
*@return
*/String getText();/*** 设置要被解析的语句*/
voidsetText(String text);
}packagecom.spring.sxf.study.springtradedao.mysql.language;/*** 词汇表
* 1、一般标识
* 2、语法关键词汇*/
public enumToken {
}
View Code
语法分析器
packagecom.spring.sxf.study.springtradedao.mysql.language;importjava.util.ArrayList;importjava.util.List;/*** 语法解析器
* 主要功能
* 1、根据语法访问词法分析器,组织语法树*/
public classLanguageParse {/*** 根据词法分析器解析出语法树
*
*@paramlexer
*@return
*/Listparse(Lexer lexer){//解析SelectColumnNode
SelectColumnNode selectColumnNode=parseSelect(lexer);//解析FromTableNode
FromTableNode fromTableNode=parseFrom(lexer);//组装语法数
List nodes=new ArrayList<>();
nodes.add(newDefaultNode(selectColumnNode,fromTableNode));returnnodes;
}
SelectColumnNode parseSelect(Lexer lexer){//解析该子节点
return null;
}
FromTableNode parseFrom(Lexer lexer){//解析该子节点
return null;
}
}
View Code
语法树及语法树的节点
packagecom.spring.sxf.study.springtradedao.mysql.language;/*** 语法树的节点
* 1、根据语法解析器解析出语法树
* 2、允许访问者进行访问*/
public interfaceNode {/*** 允许访问器访问语法树
*@paramvisitor*/
voidaccept(Visitor visitor);
}packagecom.spring.sxf.study.springtradedao.mysql.language;importjava.util.List;/*** 查询类名的节点*/
public abstract class SelectColumnNode extendsNodeImpl{/*** 获取查询的列名集合
*@return
*/
abstract ListgetColumns();
@Overridevoidaccept0(Visitor visitor) {//获取所有列名
visitor.visit(this);
}
}packagecom.spring.sxf.study.springtradedao.mysql.language;importjava.util.List;/*** 查询表名的节点*/
public abstract class FromTableNode extendsNodeImpl {/*** 获取查询的表名集合
*
*@return
*/
abstract ListgetTableName();
@Overridevoidaccept0(Visitor visitor) {
visitor.visit(this);
}
}packagecom.spring.sxf.study.springtradedao.mysql.language;/*** 模拟一个语法树
* 该语法树有两个子节点:SelectColumnNode和FromTableNode*/
public class DefaultNode extendsNodeImpl {privateSelectColumnNode selectColumnNode;privateFromTableNode fromTableNode;publicDefaultNode(SelectColumnNode selectColumnNode, FromTableNode fromTableNode) {this.selectColumnNode =selectColumnNode;this.fromTableNode =fromTableNode;
}
@Overridevoidaccept0(Visitor visitor) {//访问自身
visitor.visit(this);//访问孩子节点
selectColumnNode.accept(visitor);
fromTableNode.accept(visitor);
}
}
View Code
访问器
packagecom.spring.sxf.study.springtradedao.mysql.language;importjava.util.List;/*** 模拟一个语法树访问器*/
public class DefaultNodeVisitor implementsVisitor {private ListselectCloumns;private ListtableNames;
@Overridepublic voidpreVisit(Node x) {//执行前置操作
}
@Overridepublic voidendVisit(Node x) {//执行后置操作
}
@Overridepublic voidvisit(SelectColumnNode selectColumnNode) {//解析列
this.selectCloumns =selectColumnNode.getColumns();
}
@Overridepublic voidvisit(FromTableNode fromTableNode) {//解析表名
this.tableNames =fromTableNode.getTableName();
}
@Overridepublic voidvisit(DefaultNode defaultNode) {//解析语法树根节点
}
ListgetCloumns() {return this.selectCloumns;
}
ListgetTableNames() {return this.tableNames;
}
}
View Code
代码示意
packagecom.spring.sxf.study.springtradedao.mysql.language;importjava.util.List;/*** 模拟各个抽象对象之间的关系*/
public classTest {public static voidmain(String[] args) {//要被解析的自然语言
String text = "select a,b from t_order";//step1:建立词法分析器
Lexer lexer = newLexer() {
@OverridepublicToken token() {return null;
}
@Overridepublic voidnextToken() {
}
@OverridepublicString getText() {return null;
}
@Overridepublic voidsetText(String text) {
}
};
lexer.setText(text);//step2:建立语法分析器&并解析出语法树
LanguageParse languageParse=newLanguageParse();
List nodes=languageParse.parse(lexer);//step3:访问语法树,确认表名和列名
DefaultNodeVisitor visitor=newDefaultNodeVisitor();
nodes.get(0).accept(visitor);//获取列名
visitor.getCloumns();//获取表名
visitor.getTableNames();
}
}
View Code