背景
最近在学习编译原理的过程中了解到有些语言的编译器前端是利用 antlr4
实现源代码到 AST
的。那么什么是 antlr4
呢? antlr4
本身是基于 Java
开发的语法分析器生成工具,他能够根据文法规则生成对应的语法分析器,广泛应用于 DSL
构建,语言词法语法解析等领域。基于这个特性我们可以编写一个简单的 json
解析工具,什么,你问我为什么不编写一个脚本语言?我不会呀_。
说明
文章开始之前让我先介绍一下 antlr4
的使用步骤吧:
- 新建
g4
文件
antlr4
使用一种名为antlr
的语法描述语言来定义你的语法结构。你需要创建一个.g4
文件来定义你的语法。这个文件描述了你希望antlr4
解析的输入格式以及如何解析它。 - 生成语法解析器
一旦你创建了.g4
文件,你就可以使用antlr4
的工具来生成对应的语法解析器。 - 遍历
AST
树
在antlr4
中我们可以利用Visitor
方式和Listener
方式去遍历AST
树,针对每个节点做一些处理。不过对于实现一个json
解析器,这不是必须的。
定义语法文件
定义语法文件有几点需要注意:
- 文件名和
grammar
关键字后跟的语法名应该一致。 - 文法规则和词法规则可以同时存在一个文件中,但文法以小写开头,词法以大写开头。
- 在构建一个语法文件时,一般是先考虑文法,再考虑词法。就像是我们造句,先会考虑句子的结构,再往里面填词。但是实际语法分析的过程是:输入流先经过词法分析器生成匹配的词法符号流,词法符号流经过语法分析器生成匹配的语法结构。
接下来让我们一起构建一个json
的语法文件吧。
grammar JSON;
// g4文件一般以grammar开头,并且与文件名同名
json
: value EOF
;
// 定义了json的结束符及内容的语法
obj
: '{' pair (',' pair)* '}'
| '{' '}'
;
// 用obj表示json中key-vlaue形式的语法
pair
: STRING ':' value
;
// 定义了json中key-value形式的语法
arr
: '[' value (',' value)* ']'
| '[' ']'
;
// 定义了json中数组的语法
value
: STRING
| NUMBER
| obj
| arr
| 'true'
| 'false'
| 'null'
;
// 定义了json最基础的语法
STRING
: '"' (ESC | SAFECODEPOINT)* '"'
;
// 定义一个STRING词法
fragment ESC
: '\' (["\/bfnrt] | UNICODE)
;
fragment UNICODE
: 'u' HEX HEX HEX HEX
;
fragment HEX
: [0-9a-fA-F]
;
fragment SAFECODEPOINT
: ~ ["\u0000-u001F]
;
NUMBER
: '-'? INT ('.' [0-9] +)? EXP?
;
// 定义一个NUMBER词法
fragment INT
: '0' | [1-9] [0-9]*
;
// | 表示分支选项,这里表示数字类型,支持'0'开头,fragment表示
fragment EXP
: [Ee] [+-]? [0-9]+
;
WS
: [ tnr] + -> skip
;
// + 表示 匹配前一个匹配项至少一次,skip表示跳过,本句的意思是匹配到空格、制表符、回车符或换行符则跳过
测试语法文件
这里我使用 antlr4-idea
插件进行调试语法文件,输入一个 json
序列,通过右边的 Parse tree
可以看到解析出来的抽象语法树。
生成语法解析器
语法解析器有多种生成方式,我们可以通过命令生成,也可以通过idea插件去生成,这里我演示一种使用 idea
插件生成的方式(如果你也想试试,需要先装一个 antlr4-idea
插件)。
-
配置插件
在g4文件中右键选择Configure ANTLR...
按钮,进行一些简单的配置即可:
-
生成目标文件
我们可以在g4
文件中右键选择Generate ANTLR Recognizer
菜单去生成语法解析器。生成的文件如下:
通过上述步骤我们就生成了一个语法解析器,接下来我们可以打开我们熟悉的fastjson API,开始做一些不可告人的事情。
模仿(抄袭)fastjson API
新建 JSONObject
类,我们知道 json
是由一系列 key-value
构成的结构,那么我们可以继承 LinkedHashMap
,你要觉得我说的没道理,你可以看看 fastjson
的实现,我就是借鉴(抄袭)它的。具体代码如下:
package com.geely.gson;
import com.geely.gson.support.JSONLexer;
import com.geely.gson.support.JSONParser;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* @author sunlong
* @since 2023/9/12
*/
public class JSONObject extends LinkedHashMap {
public JSONObject() {
}
public JSONObject(int initialCapacity) {
super(initialCapacity);
}
public JSONObject(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
public JSONObject(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
public JSONObject(Map map) {
super(map);
}
protected JSONObject(JSONParser.ObjContext objContext) {
for (JSONParser.PairContext pairContext : objContext.pair()) {
this.put(stringWrapper(pairContext.STRING().getText()), pairContext.value());
}
}
public static JSONObject parseObject(String text) {
return parseObject(CharStreams.fromString(text));
}
public static JSONObject parseObject(ReadableByteChannel channel) throws IOException {
return parseObject(CharStreams.fromChannel(channel));
}
public static JSONObject parseObject(Path path) throws IOException {
return parseObject(CharStreams.fromPath(path));
}
public static JSONObject parseObject(InputStream inputStream) throws IOException {
return parseObject(CharStreams.fromStream(inputStream));
}
public static JSONObject parseObject(Reader reader) throws IOException {
return parseObject(CharStreams.fromReader(reader));
}
public static JSONObject parseObject(CharStream charStream) {
JSONLexer lexer = new JSONLexer(charStream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
JSONParser parser = new JSONParser(tokens);
JSONParser.ObjContext objCtx = parser.obj();
return new JSONObject(objCtx);
}
String stringWrapper(String origin) {
return origin.substring(1, origin.length() - 1);
}
public JSONObject getJSONObject(String key) {
return get(key, value -> {
JSONParser.ObjContext objContext = value.obj();
if (objContext == null) {
return null;
}
return new JSONObject(objContext);
});
}
public JSONArray getJSONArray(String key) {
return get(key, value -> {
JSONParser.ArrContext arrContext = value.arr();
if (arrContext == null) {
return null;
}
return new JSONArray(arrContext);
});
}
public String getStr(String key) {
return get(key, value -> {
TerminalNode node = value.STRING();
if (node == null) {
return null;
}
return stringWrapper(node.getText());
});
}
public Integer getInt(String key) {
return get(key, value -> {
TerminalNode node = value.NUMBER();
if (node == null) {
return null;
}
return Integer.valueOf(node.getText());
});
}
public Long getLong(String key) {
return get(key, value -> {
TerminalNode node = value.NUMBER();
if (node == null) {
return null;
}
return Long.valueOf(node.getText());
});
}
public Double getDouble(String key) {
return get(key, value -> {
TerminalNode node = value.NUMBER();
if (node == null) {
return null;
}
return Double.valueOf(node.getText());
});
}
public Float getFloat(String key) {
return get(key, value -> {
TerminalNode node = value.NUMBER();
if (node == null) {
return null;
}
return Float.valueOf(node.getText());
});
}
public Boolean getBoolean(String key) {
return get(key, value -> {
TerminalNode node = value.STRING();
if (node == null) {
return null;
}
return Boolean.valueOf(node.getText());
});
}
@SuppressWarnings("unchecked")
public R get(String key, Function convert) {
Object valueObj = this.get(key);
if (valueObj == null) {
return (R) null;
}
if (valueObj instanceof JSONParser.ValueContext) {
JSONParser.ValueContext valueContext = (JSONParser.ValueContext) valueObj;
if ("null".equalsIgnoreCase(valueContext.getText())) {
return (R) null;
}
R result = convert.apply(valueContext);
// 更新body树,下次获取不需要再次转换
this.put(key, result);
}
return (R) this.get(key);
}
public String toJSONString() {
StringBuilder sb = new StringBuilder();
List list = new ArrayList(this.size());
for (Map.Entry entry : this.entrySet()) {
String key = entry.getKey();
Object object = entry.getValue();
String value;
if (object == null) {
value = null;
} else if (object instanceof String) {
value = """ + object + """;
} else if (object instanceof JSONObject) {
value = object.toString();
} else if (object instanceof JSONArray) {
value = object.toString();
} else if (object instanceof Integer) {
value = object.toString();
} else if (object instanceof Double) {
value = object.toString();
} else if (object instanceof Float) {
value = object.toString();
} else if (object instanceof Boolean) {
value = object.toString();
} else if (object instanceof Long) {
value = object.toString();
} else {
value = ((JSONParser.ValueContext) object).getText();
}
list.add(""" + key + "":" + value);
}
sb.append("{");
sb.append(String.join(",", list));
sb.append("}");
return sb.toString();
}
@Override
public String toString() {
return toJSONString();
}
}
我们知道 json
里的 value
类型支持数组,所以接下来我们需要实现一个 JSONArray
类,啊,你问我为什么这个名字这么熟悉?没错, fastjson
也是这个名字:
package com.geely.gson;
import com.geely.gson.support.JSONLexer;
import com.geely.gson.support.JSONParser;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author sunlong
* @since 2023/9/12
*/
public class JSONArray extends ArrayList {
public JSONArray() {
}
public JSONArray(int initialCapacity) {
super(initialCapacity);
}
public JSONArray(Collection collection) {
super(collection);
}
protected JSONArray(JSONParser.ArrContext arrayCtx) {
addAll(arrayCtx.value()
.stream()
.map(valueContext -> new JSONObject(valueContext.obj()))
.collect(Collectors.toList()));
}
public static JSONArray parseArray(String text) {
JSONLexer lexer = new JSONLexer(CharStreams.fromString(text));
CommonTokenStream tokens = new CommonTokenStream(lexer);
JSONParser parser = new JSONParser(tokens);
JSONParser.ArrContext arrContext = parser.arr();
return new JSONArray(arrContext);
}
public String toJSONString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
List strings =
this.stream()
.map(JSONObject::toJSONString)
.collect(Collectors.toList());
sb.append(String.join(",", strings));
sb.append("]");
return sb.toString();
}
@Override
public String toString() {
return toJSONString();
}
}
再包装一个更人性化的 Exception
,屏蔽掉 antlr4
内置的异常:
package com.geely.gson.exception;
/**
* @author sunlong
* @since 2023/9/12
*/
public class JsonException extends RuntimeException {
public JsonException(String message) {
super(message);
}
}
好,如果你看到这里,那么咱们的fast
json就差不多完成了,接下来是骡子是马,让我们牵出来遛遛吧:
package com.geely.gson;
import java.io.IOException;
import java.io.InputStream;
/**
* @author sunlong
* @since 2023/9/12
*/
public class Main {
public static void main(String[] args) throws IOException {
String json ="{n" +
" "k1": "v1",n" +
" "k2": [n" +
" {n" +
" "k21": 3,n" +
" "k22": true,n" +
" "k23": null,n" +
" "k24": 1.5n" +
" }n" +
" ]n" +
"}n";
JSONObject root = JSONObject.parseObject(json);
root.put("test", 1111);
String k1 = root.getStr("k1");
JSONArray k2 = root.getJSONArray("k2");
for (JSONObject jsonObject : k2) {
System.out.println(jsonObject.toJSONString());
}
System.out.println(root);
}
}
输出结果如下:
perfect,洗洗睡觉。