自己写的java版的json解析器详解
前言
上回书说道,我用flex&bison写了个json解析的原理性示例,结果我那坑爹同事连看都不看一眼,我感到很桑心……
为了让这个同事能服我,我一定要写个java版的json解析……到时候一定让这个同事给我发一个大写的“服”字给我……
那同事还说,能写java版的json解析就可以去阿里工作了……我至今都觉得这是讽刺阿里没人才的高端黑……
因为,你看完了这篇文章,估计你也就能写个java版的json解析了……到时候咱一起去把阿里的门槛踏破呵~
另外,好多人不注意看文档结构……我发博客,后面肯定是会附上完整下载链接的……不想看文可以直接拖到后面下下来玩~
这次的程序有点长,我可能就不会粘贴所有代码了,提前告诉大家可以先下载后看文~
总体介绍
首先,这仍然是一个以探究原理为主的实现,结构清晰是第一,然后兼顾效率,我并不是想写一个比已有解析器厉害的东西……
当然,我自认为暂时还是没能力写出比已有的json第三方解析包厉害的东西……
项目的名字已经很明确了,multTravJsonParse——基于多次遍历的JSON解析……
其次,说一下,整个JSON解析使用的是遍历分析(词法解析)、状态机(语法解析)和堆栈(状态控制和操作),并非采用递归方式来实现的多次遍历解析……在语法分析时,是采用堆栈构造的……这跟递归是等效的,但是却可以免受JVM调用堆栈溢出问题的困扰……
先说一说文件结构吧,huaying1988神马鬼?不要在意这些细节,那是我因为身份证过期至今没备案成功的域名……
G:.
└─com
└─huaying1988
├─multTravJsonParse
│ │ JSON.java
│ │
│ ├─lex
│ │ JsonLex.java
│ │ TOK.java
│ │ Token.java
│ │ UnexpectedException.java
│ │
│ └─syntax
│ Operator.java
│ OPT.java
│ STATE.java
│ StateMachine.java
│
└─test
TestJsonLex.java
TestJSONParse.java
结构很明显,分为四部分:
- JSON.java——这是整个解析器的入口类,里面就一个静态方法parse,内容也很简单,就是创建StateMachine对象并调用parse方法,这个类大家都能看懂,不会再下面再详细讲了……
- lex包——这个很明显,是词法解析的包,里面定义了一个异常类,一个包含Token类型常量的TOK类,一个用来存储解析结果单元的Token类,还有一个词法解析器JsonLex类
- syntax包——也很明显,这个是语法解析的包,里面包含了一个包含状态常量定义的STATE类,一个状态机StateMachine类,一个包含操作常量OPT类,以及一个用来操作的Operator类
- test包——很明显,用来测试的,包含一个测试语法解析的TestJsonLex类,一个测试JSON解析的TestJSONParse类,这个类为了测试对比,引入了第三方的JSON解析包,那就是阿里某大牛的fastJSON包~
好了,总体的结构就是这样子,下面来一个一个的分析~
词法解析器
lex包里一共就四个文件,自定义的这个异常类我就不说了,主要是用来报错的时候好定位的……因为lex是整个json解析中离代码最近的一个处理,所以,所有的异常最后还是要在词法分析处生成比较好,因为它比较容易记录当前的行号和列号以及总字符数,报错的时候也容易告诉用户这些信息,所以JsonLex中有generateUnexpectedException方法,行号和列开头的堆栈,还有在nextChar和revertChar里面有关于行号的列号相关的处理操作……于是关于这个异常类,我就不贴代码了,目的很明确,大家估计也看得懂……
首先,要从TOK这个类开始,这里面记载类总共有多少个Token类型,当然,这个Token类型时从上一篇博客中json.l中直接扒过来的……
TOK.java代码
package com.huaying1988.multTravJsonParse.lex;
/**
* 保存Token类型信息以及相关静态变量
*
* @author huaying1988.com
*
*/
public class TOK {
public static final int STR = 0;
public static final int NUM = 1;
public static final int DESC = 2;
public static final int SPLIT = 3;
public static final int ARRS = 4;
public static final int OBJS = 5;
public static final int ARRE = 6;
public static final int OBJE = 7;
public static final int FALSE = 8;
public static final int TRUE = 9;
public static final int NIL = 10;
public static final int BGN = 11;
public static final int EOF = 12;
/**
* 并非tok类型,存储tok类型的个数,添加类型时请同步修改
*/
public static final int TOK_NUM = 13;
/**
* 将tok类型转换为字符串的转换数组,添加类型时请同步修改
*/
public static final String[] CAST_STRS = { "STR", "NUM", "DESC", "SPLIT",
"ARRS", "OBJS", "ARRE", "OBJE", "FALSE", "TRUE", "NIL", "BGN", "EOF" };
/**
* 将tok类型转换为字符串的转换数组,添加类型时请同步修改
*/
public static final String[] CAST_LOCAL_STRS = { "字符串", "数字", ":", ",",
"[", "{", "]", "}", "false", "true", "null", "开始", "结束" };
/**
* 将tok类型转换为String,测试用显示结果的
*
* @return
*/
public static String castTokType2Str(int type) {
if (type < 0 || type > TOK_NUM)
return "undefine";
else
return CAST_STRS[type];
}
/**
* 将tok类型转换为String,用于报错信息
*
* @return
*/
public static String castTokType2LocalStr(int type) {
if (type < 0 || type > TOK_NUM)
return "undefine";
else
return CAST_LOCAL_STRS[type];
}
}
里面保存了13个token类型,同时还包括对类型转换为字符串的方法……
之后便是重头戏,JsonLex.java,刚才说了它要记录行列字符进行字符串遍历(nextChar)、遍历控制(reverseChar),当然,词法解析才是这个类的重点。
package com.huaying1988.multTravJsonParse.lex;
import java.util.Stack;
/**
* Json词法分析器
*
* @author huaying1988.com
*
*/
public class JsonLex {
// 当前行号
private int lineNum = 0;
// 用于记录每一行的起始位置
private Stack<Integer> colMarks = new Stack<Integer>();
// 用于报错的行游标
private int startLine = 0;
// 用于报错的列游标
private int startCol = 0;
// 当前字符游标
private int cur = -1;
// 保存当前要解析的字符串
private String str = null;
// 保存当前要解析的字符串的长度
private int len = 0;
/**
* JsonLex构造函数
*
* @param str
* 要解析的字符串
*/
public JsonLex(String str) {
if (str == null)
throw new NullPointerException("词法解析构造函数不能传递null");
this.str = str;
this.len = str.length();
this.startLine = 0;
this.startCol = 0;
this.cur = -1;
this.lineNum = 0;
this.colMarks.push(0);
}
public char getCurChar(){
if (cur >= len - 1) {
return 0;
}else{
return str.charAt(cur);
}
}
public Token parseSymbol(char c) {
switch (c) {
case '[':
return Token.ARRS;
case ']':
return Token.ARRE;
case '{':
return Token.OBJS;
case '}':
return Token.OBJE;
case ',':
return Token.SPLIT;
case ':':
return Token.DESC;
}
return null;
}
public boolean isLetterUnderline(char c) {
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_');
}
public boolean isNumLetterUnderline(char c) {
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9') || c == '_');
}
public boolean isNum(char c) {
return (c >= '0' && c <= '9');
}
public boolean isDecimal(char c) {
return ((c >= '0' && c <= '9') || (c == '.'));
}
private void checkEnd() {
if (cur >= len - 1) {
throw generateUnexpectedException("未预期的结束,字符串未结束");
}
}
public UnexpectedException generateUnexpectedException(String str) {
return new UnexpectedException(cur,startLine, startCol, str);
}
public UnexpectedException generateUnexpectedException(String str,
Throwable e) {
return new UnexpectedException(cur,startLine, startCol, str, e);
}
private String getStrValue(char s) {
int start = cur;
char c;
while ((c = nextChar()) != 0) {
if (c == '\\') {// 跳过斜杠以及后面的字符
c = nextChar();
} else if (s == c) {
return str.substring(start + 1, cur);
}
}
checkEnd();
return null;
}
private String getNumValue() {
int start = cur;
char c;
while ((c = nextChar()) != 0) {
if (!isDecimal(c)) {
return str.substring(start, revertChar());
}
}
checkEnd();
return null;
}
private Token getDefToken() {
int start = cur;
char c;
while ((c = nextChar()) != 0) {
if (!isNumLetterUnderline(c)) {
String value = str.substring(start, revertChar());
if ("true".equals(value)) {
return Token.TRUE;
} else if ("false".equals(value)) {
return Token.FALSE;
} else if ("null".equals(value)) {
return Token.NIL;
} else {
return new Token(TOK.STR, value);
}
}
}
checkEnd();
return null;
}
/**
* 获取下一个字节,同时进行 行、列 计数
*
* @return 下一个字节,结束时返回0
*/
private char nextChar() {
if (cur >= len-1) {
return 0;
}
++cur;
char c = str.charAt(cur);
if (c == '\n') {
++lineNum;
colMarks.push(cur);
}
return c;
}
/**
* 撤回一个字节,同时进行 行、列 计数,返回撤回前的字符游标
*
* @return 下一个字节,结束时返回0
*/
private int revertChar() {
if (cur <= 0) {
return 0;
}
int rcur = cur--;
char c = str.charAt(rcur);
if (c == '\n') {
--lineNum;
colMarks.pop();
}
return rcur;
}
public static boolean isSpace(char c) {
return (c == ' ' || c == '\t' || c == '\n');
}
// str \"(\\\"|[^\"])*\"
// def [_a-zA-Z][_a-zA-Z0-9]*
// num -?[0-9]+(\.[0-9]+)?
// space [ \t\n]+
/**
* 获取下一个Token的主函数
*/
public Token next() {
if (lineNum == 0) {
lineNum = 1;
return Token.BGN;
}
char c;
while ((c = nextChar()) != 0) {
startLine = lineNum;
startCol = getColNum();
if (c == '"' || c == '\'') {
return new Token(TOK.STR, getStrValue(c));
} else if (isLetterUnderline(c)) {
return getDefToken();
} else if (isNum(c) || c=='-') {
return new Token(TOK.NUM, getNumValue());
} else if (isSpace(c)) {
continue;
} else {
return parseSymbol(c);
}
}
if (c == 0) {
return Token.EOF;
}
return null;
}
public int getLineNum() {
return lineNum;
}
public int getColNum() {
return cur - colMarks.peek();
}
public int getCur() {
return cur;
}
public String getStr() {
return str;
}
public int getLen() {
return len;
}
}
构造函数初始化就不说了……一些简单的Get/Set、字符判断也不说了……词法解析的主函数入口是这个类中的next,负责返回下一个Token……
同事问我Token是啥……Token就是一个实体对象类,里面主要存了两个属性,类型和值……词法解析的主要作用是预处理,将复杂的字符串转换为相对简单而统一的类型,而这个统一类型,我们把它称之为Token,而词法解析就是讲字符流转换为Token流的一个过程……我想,这么说同事应该能听懂……
当然,大部分的Token只需要类型就行了,只有少数复杂的Token类型有值,那就是STR(字符串)、NUM(数字)这两种类型的Token……
Next这个方法循环调用nextChar获取下一个字符,碰见某种类型的初始字符,就开始进入相应Token类型的处理函数中,最终返回Token类型的对象……
其中parseSymbol,字符类型的Token没什么好说的……最麻烦的就是STR(字符串)、NUM(数字)这两种类型,它们除了有类型还有值,处理函数分别是getStrValue和getNumValue……行数都不多,大约看看也能懂……空格字符略过,没什么好说的……除此之外,还有一个getDefToken,这个一方面是用来处理true、false、null的,看过上篇文章中json.l文件的大约能懂这个是啥……另一方面是针对不严格JSON中的key的……因为我经常写不严格的JSON,所以,这个要能处理,可以看到,这种情况作为STR(字符串)类型处理了……
最后看看Token这个实体类:
package com.huaying1988.multTravJsonParse.lex;
/**
* 保存token
*
* @author huaying1988.com
*
*/
public class Token {
public static final Token DESC = new Token(TOK.DESC);
public static final Token SPLIT = new Token(TOK.SPLIT);
public static final Token ARRS = new Token(TOK.ARRS);
public static final Token OBJS = new Token(TOK.OBJS);
public static final Token ARRE = new Token(TOK.ARRE);
public static final Token OBJE = new Token(TOK.OBJE);
public static final Token FALSE = new Token(TOK.FALSE);
public static final Token TRUE = new Token(TOK.TRUE);
public static final Token NIL = new Token(TOK.NIL);
public static final Token BGN = new Token(TOK.BGN);
public static final Token EOF = new Token(TOK.EOF);
// 从TOK类中定义的类型
private Integer type;
// 该tok的值
private String value;
public Token(int type) {
this.type = type;
this.value = null;
}
public Token(int type, String value) {
this.type = type;
this.value = value;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public static String unescape(String str) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '\\') {
c = str.charAt(++i);
switch (c) {
case '"':
sb.append('"');
break;
case '\\':
sb.append('\\');
break;
case '/':
sb.append('/');
break;
case 'b':
sb.append('\b');
break;
case 'f':
sb.append('\f');
break;
case 'n':
sb.append('\n');
break;
case 'r':
sb.append('\r');
break;
case 't':
sb.append('\t');
break;
case 'u':
String hex = str.substring(i+1, i+5);
sb.append((char)Integer.parseInt(hex, 16));
i+=4;
break;
default:
throw new RuntimeException("“\\”后面期待“\"\\/bfnrtu”中的字符,结果得到“"+c+"”");
}
}else{
sb.append(c);
}
}
return sb.toString();
}
public Object getRealValue(){
Object curValue = null;
switch(this.getType()){
case TOK.TRUE:
curValue = true;
break;
case TOK.FALSE:
curValue = false;
break;
case TOK.NIL:
curValue = null;
break;
case TOK.NUM:
if(value.indexOf('.')>=0){
curValue = Double.parseDouble(value);
}else{
curValue = Integer.parseInt(value);
}
break;
case TOK.STR:
curValue = unescape(value);
break;
}
return curValue;
}
public String toString() {
if (this.type > 1) {
return "[" + TOK.castTokType2Str(this.type) + "]";
} else {
return "[" + TOK.castTokType2Str(this.type) + ":" + this.value
+ "]";
}
}
public String toLocalString() {
if (this.type > 1) {
return "“" + TOK.castTokType2LocalStr(this.type) + "”";
} else {
return "“" + TOK.castTokType2LocalStr(this.type) + ":" + this.value
+ "”";
}
}
}
这个实体类把所有没有值得Token都定义了静态常量,这个无非就是刚才说的兼顾效率,每次都new没什么意思,当然第二点是为了对比判断方便……
对于不含值的Token类型可以直接用==判断,因为两个指向的是同一个对象,所以能直接进行指针判断,也是挺爽的不是……尤其是对于EOF(文件结束)类型的Token,判断循环结束条件尤其好用……
除了这些静态常量,还有刚才说的类型、值两个属性,get/set方法,还有toString方法之外,不得不提的是getRealValue和unescape这两个方法……
有值的就那么几个……NUM类型还判断了一下是int类型还是double类型……这些都不难理解……unescape方法是将字符串中的转义字符化解掉……变成真正实际的字符串……
而这个unscape方法就是传说中的对字符串的第二次遍历……对应项目名多次遍历,这就是其中一个点……如果设计巧妙点可以一次性的解决STR(字符串)、NUM(数字)类型的值得问题,但是,就当前项目来说,写到这份上,也是可以了……也不枉担一个多次遍历的名分……
这样,整个词法分析器就讲完了……貌似没什么太难以理解的地方……跟上篇文章一样,词法解析一般都不成难点,等你从词法分析入坑之后才发现:麻烦才刚刚开始……
语法解析器
接下来的事情变得高级起来了……高级得有时候一下子就不懂了……更确切的说是——太抽象了……
编译原理难学就在于它太抽象了……用到了很多极端抽象的东西……比如说,下面要说的状态自动机就是这么个东西……
上篇文章已经说了,这个JSON解析器的总体实现都是一晚上搞出来的东西……这一晚上在语法解析器这儿,我曾经设想过好多的方案……
比如说采用yacc/bison那样语法树的输入形式,然后进行一个树状遍历……但是我终究没这么干……因为我毕竟不想写一个像yacc/bison那样的通用解析器……
于是我选择了状态自动机……说起状态自动机来,这是编译原理里面比较晦涩的一个东西……但是当我把JSON演变的状态自动机画出来,也不过如此了……
画的不好看,很乱,其中还可能有错误,但是不想改了,维持原样吧……这个图是第二天给那个同事讲解的时候画的,然而因为那天晚上熬到很晚,所以第二天画这个图的时候精神状态自然也不怎么样,大家凑合着看吧:
同事问,你在实现这个的时候,是先画了这么张图么?当然——没有……我要是画了这么张图,也不至于花了一晚上去实现啊……
这是先实现的,后有了这张图啊……看不懂?没关系,接着再往下看,慢慢就懂了……
先看一下状态定义类STATE.java:
package com.huaying1988.multTravJsonParse.syntax;
import java.lang.reflect.Method;
import com.huaying1988.multTravJsonParse.lex.TOK;
/**
* 保存状态列表、状态转换矩阵等静态常量
* @author huaying1988.com
*
*/
public class STATE {
//开始态
public static final Integer BGN = 0;
//数组值前态
public static final Integer ARRBV = 1;
//数组值后态
public static final Integer ARRAV = 2;
//对象键前态
public static final Integer OBJBK = 3;
//对象键后态
public static final Integer OBJAK = 4;
//对象值前态
public static final Integer OBJBV = 5;
//对象值后态
public static final Integer OBJAV = 6;
//结果态
public static final Integer VAL = 7;
//结束态
public static final Integer EOF = 8;
//错误态
public static final Integer ERR = 9;
//状态机的状态转换矩阵
public static final Integer[][] STM = {
/*INPUT——STR NUM DESC SPLIT ARRS OBJS ARRE OBJE FALSE TRUE NIL BGN*/
/*BGN*/ {VAL,VAL,ERR,ERR,ARRBV,OBJBK,ERR,ERR,VAL,VAL,VAL,BGN},
/*ARRBV*/{ARRAV,ARRAV,ERR,ERR,ARRBV,OBJBK,VAL,ERR,ARRAV,ARRAV,ARRAV,ERR},
/*ARRAV*/{ERR,ERR,ERR,ARRBV,ERR,ERR,VAL,ERR,ERR,ERR,ERR,ERR},
/*OBJBK*/{OBJAK,OBJAK,ERR,ERR,ERR,ERR,ERR,VAL,ERR,ERR,ERR,ERR},
/*OBJAK*/{ERR,ERR,OBJBV,ERR,ERR,ERR,ERR,ERR,ERR,ERR,ERR,ERR},
/*OBJBV*/{OBJAV,OBJAV,ERR,ERR,ARRBV,OBJBK,ERR,ERR,OBJAV,OBJAV,OBJAV,ERR},
/*OBJAV*/{ERR,ERR,ERR,OBJBK,ERR,ERR,ERR,VAL,ERR,ERR,ERR,ERR},
/*VAL*/{},//没有后续状态,遇见此状态时弹出状态栈中的状态计算当前状态,占位,方便后期添加
/*EOF*/{},//没有后续状态,占位,方便后期添加
/*ERR*/{}//没有后续状态,占位,方便后期添加
};
//Token输入操作列表
/*INPUT —— STR NUM DESC SPLIT ARRS OBJS ARRE OBJE FALSE TRUE NIL BGN*/
public static final Method[] TKOL = {
null,null,null,null,OPT.ARRS,OPT.OBJS,null,null,null,null,null,null
};
//目标状态转换操作列表
/*TO:BGN ARRBV ARRAV OBJBK OBJAK OBJBV OBJAV VAL EOF ERR*/
public static final Method[] STOL = {
null,null,OPT.ARRAV,null,OPT.OBJAK,null,OPT.OBJAV,OPT.VAL,null,null
};
//期望Token描述列表
/*FROM:BGN ARRBV ARRAV OBJBK OBJAK OBJBV OBJAV VAL EOF ERR*/
public static final String[] ETS = {
getExpectStr(BGN), getExpectStr(ARRBV), getExpectStr(ARRAV), getExpectStr(OBJBK), getExpectStr(OBJAK), getExpectStr(OBJBV), getExpectStr(OBJAV),TOK.castTokType2LocalStr(TOK.EOF),TOK.castTokType2LocalStr(TOK.EOF),TOK.castTokType2LocalStr(TOK.EOF)
};
//状态描述列表
/*BGN ARRBV ARRAV OBJBK OBJAK OBJBV OBJAV VAL EOF ERR*/
public static final String[] STS = {
"解析开始","数组待值","数组得值","对象待键","对象得键","对象待值","对象得值","得最终值","解析结束","异常错误"
};
//将状态数值转换为状态描述
public static String castLocalStr(Integer s){
return STS[s];
}
//获取期望Token描述字符串
public static String getExpectStr(Integer old){
StringBuffer sb = new StringBuffer();
for(int i=0;i<STM[old].length;i++){
Integer s = STM[old][i];
if(s != ERR){
sb.append(TOK.castTokType2LocalStr(i)).append('|');
}
}
return sb.length() == 0 ? null : sb.deleteCharAt(sb.length()-1).toString();
}
}
这个类里面分为这么几部分:
- 状态的定义,全是静态常量,凑了个整数,正好10个
- 一个状态转换矩阵的定义,行是状态,列是输入的Token,整个矩阵的意义是:该行的状态在遇见该列的Token类型时转换为什么状态
- 两个操作列表,一个是Token输入操作列表,意思是,当输入为这个类型的Token时,我要执行一个什么操作;还有一个是目标状态转换操作列表,意思是,如果转换为这个状态的话,我会执行什么操作。所映射的操作与OPT和Operator这两个类相关,后面再说……
- 一个期望Token描述列表以及对应的一个实现方法,这个列表里存的是,当前状态下输入哪些Token是正确的,其实也就是从状态转换矩阵里,把状态对应行中不是ERR(错误)的Token取出来连成字符串,这个字符串列表是为报错用的……如果出现错误,我们就可以提示用户:在哪一行哪一列,期望什么,但是却得到了什么……
- 将状态转换为字符串的列表及方法,对应数字就能找到相应的描述,再对应上面那个图,大体上就会有感觉了……
相信,有了这个描述,再加上这个状态转换矩阵对应上面状态转换图一起看,大家应该都会对状态机有一个整体的概念和把握了……
接下来就是状态机的实现了……看StateMachine类,反而很简单,就一个方法……比起词法解析来代码少多了……先看一下内容:
package com.huaying1988.multTravJsonParse.syntax;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.huaying1988.multTravJsonParse.lex.JsonLex;
import com.huaying1988.multTravJsonParse.lex.Token;
import com.huaying1988.multTravJsonParse.lex.UnexpectedException;
/**
* 语法状态机,负责状态转换,以及相关操作的调用
* @author huaying1988.com
*
*/
public class StateMachine {
private JsonLex lex = null;
private Operator opt = null;
private Integer status = null;
public StateMachine(String str){
if (str == null)
throw new NullPointerException("语法解析构造函数不能传递null");
lex = new JsonLex(str);
opt = new Operator(lex);
}
public Object parse(){
Token tk = null;
status = STATE.BGN;
Integer oldStatus = status;
while((tk=lex.next())!=Token.EOF){
if(tk == null){
throw lex.generateUnexpectedException("发现不能识别的token:“" + lex.getCurChar() + "”");
}
if(status == STATE.VAL || status == STATE.EOF || status == STATE.ERR){
throw lex.generateUnexpectedException("当前状态【"+STATE.castLocalStr(oldStatus)+"】,期待【结束】;却返回"+tk.toLocalString());
}
oldStatus = status;
status = STATE.STM[oldStatus][tk.getType()];
if(status == STATE.ERR){
throw lex.generateUnexpectedException("当前状态【"+STATE.castLocalStr(oldStatus)+"】,期待【"+(STATE.ETS[oldStatus]==null?"结束":STATE.ETS[oldStatus])+"】;却返回"+tk.toLocalString());
}
try {
Method m = STATE.TKOL[tk.getType()];
if(m!=null){//输入Token操作
status = (Integer)m.invoke(opt, oldStatus, status, tk);
}
m = STATE.STOL[status];
if(m!=null){//目标状态操作
status = (Integer)m.invoke(opt, oldStatus, status, tk);
}
} catch (IllegalArgumentException e) {
throw lex.generateUnexpectedException("【反射调用】传入非法参数",e);
} catch (IllegalAccessException e) {
throw lex.generateUnexpectedException("【反射调用】私有方法无法调用",e);
} catch (InvocationTargetException e) {
if(e.getTargetException() instanceof UnexpectedException){
throw (UnexpectedException)e.getTargetException();
}else{
throw lex.generateUnexpectedException("运行时异常",e);
}
}
}
return opt.getCurValue();
}
}
俗话说,浓缩的都是精华啊……除了构造函数初始化,就剩下parse方法了……JSON.java类就是调的这个方法……看来这就已经到了核心方法了!
然而这个核心方法也没几行啊……核心内容就是一个while循环,从词法分析器的上游获得Token作为输入……看看是不是文件结束类型的Token,是,就跳出循环,返回操作对象中的当前值……
每次循环,开始先做一些检查,该报错的报错,根据状态转换矩阵进行状态转换,当前得到三个参数,一个旧状态,一个输入的Token,一个新状态,再做一次ERR检查,该报错的报错……
然后,看看Token操作列表里有该Token类型相关的绑定操作没,有的话执行,没有的话跳过;再看看状态操作列表里有没有目标状态绑定的操作,有的话执行,传值都是这三个参数:一个旧状态,一个输入的Token,一个新状态。
这个地方用的反射执行,为啥没用接口类的多重实现的方式呢?因为那样写太不紧凑了……如果我每一个操作都写一个操作接口的操作类实现的话,我要写好多的类,这样一点都不漂亮,而且把代码搞得很分散,根本不容易看,更不好给大家讲解不是?所以,用反射的方式,一个类里可以包含所有你想要的方法,所有的实现都在一个类里,多漂亮……这就叫用一种形式上的完美来代替另一种形式上的完美……
上篇文章我说过了,语法分析是一道坎……这个核心弄懂了,大约整个解析器就弄得差不多了……不管你信不信,反正经过我的讲解之后,那个同事把状态机、状态转换矩阵突然茅塞顿开般的弄懂了,这是很大的进步……
然而,后面还有一道坎,你懂得~
配套堆栈处理
状态机有了,但是总要用它来干点事,那就是对应的操作了……
刚才已经看到了,所有的操作映射都挂在了STATE类里……
其实到这一块,就跟上一篇文章又很像了……
因为语法分析上,yacc/bison用了一种通用的抽象描述手段,语法树;而我用了一种通用的抽象解析手段,状态机……
其本质是一样的……最后回归到操作上,连形式也一样了……
先看一下操作类Operator.java:
package com.huaying1988.multTravJsonParse.syntax;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import com.huaying1988.multTravJsonParse.lex.JsonLex;
import com.huaying1988.multTravJsonParse.lex.TOK;
import com.huaying1988.multTravJsonParse.lex.Token;
/**
* 该类负责实际操作
*
* @author huaying1988.com
*
*/
public class Operator {
private JsonLex lex = null;
private Stack<Integer> statusStack = new Stack<Integer>();
private Object curValue = null;
private Stack<Object> keyStack = new Stack<Object>();
private Stack<Object> objStack = new Stack<Object>();
private Object curObj = null;
public Object getCurObj() {
return curObj;
}
public Operator(JsonLex lex) {
this.lex = lex;
}
public Object getCurValue(){
return curValue;
}
public Integer objs(Integer from, Integer to, Token input) {
if (from != STATE.BGN) {
statusStack.push(from);
}
curObj = new HashMap<Object, Object>();
objStack.push(curObj);
return to;
}
public Integer arrs(Integer from, Integer to, Token input) {
if (from != STATE.BGN) {
statusStack.push(from);
}
curObj = new ArrayList<Object>();
objStack.push(curObj);
return to;
}
@SuppressWarnings("unchecked")
public Integer val(Integer from, Integer to, Token input) {
switch (input.getType()) {
case TOK.ARRE:
case TOK.OBJE:
curObj = objStack.pop();
curValue = curObj;
break;
case TOK.TRUE:
case TOK.FALSE:
case TOK.NIL:
case TOK.NUM:
case TOK.STR:
curValue = getRealValue(input);
break;
}
if (statusStack.isEmpty()) {
return STATE.EOF;
}else{
Integer s = statusStack.pop();
if (s == STATE.ARRBV) {
curObj = objStack.peek();
((List<Object>) curObj).add(curValue);
s = STATE.ARRAV;
} else if (s == STATE.OBJBV) {
curObj = objStack.peek();
((Map<Object, Object>) curObj).put(keyStack.pop(), curValue);
s = STATE.OBJAV;
}
return s;
}
}
private Object getRealValue(Token input) {
Object value = null;
try {
value = input.getRealValue();
} catch (RuntimeException e) {
lex.generateUnexpectedException("字符串转换错误", e);
}
return value;
}
@SuppressWarnings("unchecked")
public Integer arrav(Integer from, Integer to, Token input) {
curValue = getRealValue(input);
((List<Object>) curObj).add(curValue);
return to;
}
public Integer objak(Integer from, Integer to, Token input) {
keyStack.push(getRealValue(input));
return to;
}
@SuppressWarnings("unchecked")
public Integer objav(Integer from, Integer to, Token input) {
curValue = getRealValue(input);
((Map<Object, Object>) curObj).put(keyStack.pop(), curValue);
return to;
}
}
看过
上一篇文章的同学会觉得很眼熟吧,眼熟的东西有这么几个属性:curObj、curValue、objStack
不认识的有这么两个属性:stateStack、keyStack……
学习好的同学可能记起来了我上一篇文章中说的话:yacc/bison内置有两个栈,一个状态栈,一个值栈
而这两个Stack就是传说中的这两个栈……一一暴露在了我们的眼前……
我思考前后,觉得还是把这个栈放在这个类里比较好,因为想想yacc/bison里面这些东西都是紧密相关的……
因为这两个栈本来也不属于状态机的东西……
好了,除了这几个属性,还有get方法,剩下的,都是用于操作的方法了……
什么出栈入栈啦……来回赋值啦……put、add啦……跟上篇文章写的js生成如出一辙,异曲同工……
唯一与状态机沾点边的就是val这个操作,这个操作会在状态栈里拿出一个状态来进行运算后,返回一个新的状态作为状态机的新状态……
这是很重要的一步,也就是在遍历表达式树状结构的时候必要的一步状态回溯操作……学过数据结构的好好想想就能想明白……
至于其他的,没什么好说的……最后再看看OPT.java这个类:
package com.huaying1988.multTravJsonParse.syntax;
import java.lang.reflect.Method;
import com.huaying1988.multTravJsonParse.lex.Token;
/**
* 保存操作相关的静态常量
* @author huaying1988.com
*
*/
public class OPT {
public static final Method VAL = getMethod("val");
public static final Method ARRAV = getMethod("arrav");
public static final Method OBJAK = getMethod("objak");
public static final Method OBJAV = getMethod("objav");
public static final Method ARRS = getMethod("arrs");
public static final Method OBJS = getMethod("objs");
public static Method getMethod(String methodName){
Method m = null;
try {
m = Operator.class.getMethod(methodName, new Class[]{Integer.class,Integer.class,Token.class});
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return m;
}
}
这个类里就定义了Operator类中这些方法的静态映射……用来反射调用的时候用的……其中为了简便起见,还写了一个配套的方法返回相应的Method对象……