【词法分析】自定义动态语言处理框架
一、学习目的
项目研发过程中经常会需要将业务逻辑外置,需要将业务逻辑和代码分离。一般面对这样的需求有以下几种解决办法:
- 引入一个规则引擎,比如Drools。
- 利用java的javax.script.ScriptEngineManager调用javascript脚本。
- 利用antlr这样的开源项目定义自己的业务领域语言。
二、词法分析
把程序中的词法单元分为四类:标识符(分为关键字和一般标识符)、数字、特殊字符、空白(空格、Tab、回车换行等)
三、参考sql语句的词法分析和语法分析
参考:https://segmentfault.com/a/1190000008120254
Druid 的代码里,代表语法分析和词法分析的类分别是 SQLParser 和 Lexer。并且, Parser 拥有一个 Lexer。
public class SQLParser { protected final Lexer lexer; protected String dbType; public SQLParser(String sql, String dbType){ this(new Lexer(sql), dbType); this.lexer.nextToken(); } public SQLParser(String sql){ this(sql, null); } public SQLParser(Lexer lexer){ this(lexer, null); } public SQLParser(Lexer lexer, String dbType){ this.lexer = lexer; this.dbType = dbType; } }
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、各个角色之间的关系的伪代码
词法分析器
package com.spring.sxf.study.springtradedao.mysql.language; /** * 词法分析器 * 主要功能 * 1、提供词汇表 * 2、访问语句,提炼出词汇 */ public interface Lexer { /** * 获取当前token * @return */ Token token(); /** * 寻找下一个token */ void nextToken(); /** * 获取被解析的语句 * @return */ String getText(); /** * 设置要被解析的语句 */ void setText(String text); } package com.spring.sxf.study.springtradedao.mysql.language; /** * 词汇表 * 1、一般标识 * 2、语法关键词汇 */ public enum Token { }
语法分析器
package com.spring.sxf.study.springtradedao.mysql.language; import java.util.ArrayList; import java.util.List; /** * 语法解析器 * 主要功能 * 1、根据语法访问词法分析器,组织语法树 */ public class LanguageParse { /** * 根据词法分析器解析出语法树 * * @param lexer * @return */ List<Node> parse(Lexer lexer){ //解析SelectColumnNode SelectColumnNode selectColumnNode=parseSelect(lexer); //解析FromTableNode FromTableNode fromTableNode=parseFrom(lexer); //组装语法数 List<Node> nodes=new ArrayList<>(); nodes.add(new DefaultNode(selectColumnNode,fromTableNode)); return nodes; } SelectColumnNode parseSelect(Lexer lexer){ //解析该子节点 return null; } FromTableNode parseFrom(Lexer lexer){ //解析该子节点 return null; } }
语法树及语法树的节点
package com.spring.sxf.study.springtradedao.mysql.language; /** * 语法树的节点 * 1、根据语法解析器解析出语法树 * 2、允许访问者进行访问 */ public interface Node { /** * 允许访问器访问语法树 * @param visitor */ void accept(Visitor visitor); } package com.spring.sxf.study.springtradedao.mysql.language; import java.util.List; /** * 查询类名的节点 */ public abstract class SelectColumnNode extends NodeImpl{ /** * 获取查询的列名集合 * @return */ abstract List<String> getColumns(); @Override void accept0(Visitor visitor) { //获取所有列名 visitor.visit(this); } } package com.spring.sxf.study.springtradedao.mysql.language; import java.util.List; /** * 查询表名的节点 */ public abstract class FromTableNode extends NodeImpl { /** * 获取查询的表名集合 * * @return */ abstract List<String> getTableName(); @Override void accept0(Visitor visitor) { visitor.visit(this); } } package com.spring.sxf.study.springtradedao.mysql.language; /** * 模拟一个语法树 * 该语法树有两个子节点:SelectColumnNode和FromTableNode */ public class DefaultNode extends NodeImpl { private SelectColumnNode selectColumnNode; private FromTableNode fromTableNode; public DefaultNode(SelectColumnNode selectColumnNode, FromTableNode fromTableNode) { this.selectColumnNode = selectColumnNode; this.fromTableNode = fromTableNode; } @Override void accept0(Visitor visitor) { //访问自身 visitor.visit(this); //访问孩子节点 selectColumnNode.accept(visitor); fromTableNode.accept(visitor); } }
访问器
package com.spring.sxf.study.springtradedao.mysql.language; import java.util.List; /** * 模拟一个语法树访问器 */ public class DefaultNodeVisitor implements Visitor { private List<String> selectCloumns; private List<String> tableNames; @Override public void preVisit(Node x) { //执行前置操作 } @Override public void endVisit(Node x) { //执行后置操作 } @Override public void visit(SelectColumnNode selectColumnNode) { //解析列 this.selectCloumns = selectColumnNode.getColumns(); } @Override public void visit(FromTableNode fromTableNode) { //解析表名 this.tableNames = fromTableNode.getTableName(); } @Override public void visit(DefaultNode defaultNode) { //解析语法树根节点 } List<String> getCloumns() { return this.selectCloumns; } List<String> getTableNames() { return this.tableNames; } }
代码示意
package com.spring.sxf.study.springtradedao.mysql.language; import java.util.List; /** * 模拟各个抽象对象之间的关系 */ public class Test { public static void main(String[] args) { //要被解析的自然语言 String text = "select a,b from t_order"; //step1:建立词法分析器 Lexer lexer = new Lexer() { @Override public Token token() { return null; } @Override public void nextToken() { } @Override public String getText() { return null; } @Override public void setText(String text) { } }; lexer.setText(text); //step2:建立语法分析器&并解析出语法树 LanguageParse languageParse=new LanguageParse(); List<Node> nodes= languageParse.parse(lexer); //step3:访问语法树,确认表名和列名 DefaultNodeVisitor visitor=new DefaultNodeVisitor(); nodes.get(0).accept(visitor); //获取列名 visitor.getCloumns(); //获取表名 visitor.getTableNames(); } }
浙公网安备 33010602011771号