优化打印逻辑

This commit is contained in:
Luke 2025-04-27 13:57:31 +08:00
parent 4df44686e7
commit 210a6942f4
5 changed files with 544 additions and 201 deletions

View File

@ -34,14 +34,9 @@ public class Main {
// 3. 可读地打印 AST // 3. 可读地打印 AST
ASTPrinter.print(ast); ASTPrinter.print(ast);
// 1) 输出紧凑 JSON // 4. AST JSON
String compact = ASTJsonSerializer.toJsonString(ast);
// System.out.println("=== Compact JSON ===");
// System.out.println(compact);
// 2) 输出美化后的 JSON
System.out.println("=== Pretty JSON ==="); System.out.println("=== Pretty JSON ===");
System.out.println(JsonFormatter.prettyPrint(compact)); System.out.println(JsonFormatter.prettyPrint(ASTJsonSerializer.toJsonString(ast)));
} }
} }

View File

@ -4,183 +4,170 @@ import org.jcnc.snow.compiler.parser.ast.*;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.Node; import org.jcnc.snow.compiler.parser.ast.base.Node;
import java.util.List; import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* ASTJsonSerializer 工具类
* <p>
* 将编译器生成的 AST抽象语法树节点列表转换为通用的 Map/List 结构
* 并借助 JSONParser.toJson(Object) 方法序列化为 JSON 字符串
* <p>
* 支持的节点类型包括ModuleNodeFunctionNodeDeclarationNode
* AssignmentNodeIfNodeLoopNodeReturnNodeExpressionStatementNode
* 以及各种 ExpressionNode BinaryExpressionNodeIdentifierNode
*/
public class ASTJsonSerializer { public class ASTJsonSerializer {
public static String toJsonString(List<Node> ast) { /**
return ast.stream() * 快速创建一个 LinkedHashMap并写入 type 字段
.map(ASTJsonSerializer::nodeToJson) */
.collect(Collectors.joining(",", "[", "]")); private static Map<String, Object> newNodeMap(String type) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("type", type);
return m;
} }
private static String nodeToJson(Node n) { /**
return switch (n) { * 用于构建表达式节点的 Map
case ModuleNode m -> moduleToJson(m); */
case FunctionNode f -> functionToJson(f); private static Map<String, Object> exprMap(String type, Object... kv) {
case DeclarationNode d -> declarationToJson(d); Map<String, Object> m = new LinkedHashMap<>();
case AssignmentNode a -> assignmentToJson(a); m.put("type", type);
case IfNode i -> ifToJson(i); for (int i = 0; i < kv.length; i += 2) {
case LoopNode l -> loopToJson(l); m.put((String) kv[i], kv[i + 1]);
case ReturnNode r -> returnToJson(r);
case ExpressionStatementNode e -> exprStmtToJson(e);
default -> simpleTypeJson(n.getClass().getSimpleName());
};
}
private static String moduleToJson(ModuleNode m) {
return """
{"type":"Module","name":%1$s,"imports":%2$s,"functions":%3$s}
""".formatted(
quote(m.name()),
listToJsonArray(m.imports(),
imp -> String.format("{\"type\":\"Import\",\"module\":%s}", quote(imp.moduleName()))
),
listToJsonArray(m.functions(), ASTJsonSerializer::functionToJson)
);
}
private static String functionToJson(FunctionNode f) {
return """
{"type":"Function","name":%1$s,"parameters":%2$s,"returnType":%3$s,"body":%4$s}
""".formatted(
quote(f.name()),
listToJsonArray(f.parameters(),
p -> String.format("{\"name\":%s,\"type\":%s}", quote(p.name()), quote(p.type()))
),
quote(f.returnType()),
listToJsonArray(f.body(), ASTJsonSerializer::nodeToJson)
);
}
private static String declarationToJson(DeclarationNode d) {
var base = new StringBuilder();
base.append("""
{"type":"Declaration","name":%1$s,"varType":%2$s
""".formatted(quote(d.getName()), quote(d.getType())));
d.getInitializer().ifPresent(init ->
base.append(",\"initializer\":").append(exprToJson(init))
);
base.append("}");
return base.toString();
}
private static String assignmentToJson(AssignmentNode a) {
return """
{"type":"Assignment","variable":%1$s,"value":%2$s}
""".formatted(quote(a.variable()), exprToJson(a.value()));
}
private static String ifToJson(IfNode i) {
var thenJson = listToJsonArray(i.thenBranch(), ASTJsonSerializer::nodeToJson);
var elseJson = i.elseBranch().isEmpty() ? ""
: ",\"else\":" + listToJsonArray(i.elseBranch(), ASTJsonSerializer::nodeToJson);
return """
{"type":"If","condition":%1$s,"then":%2$s%3$s}
""".formatted(exprToJson(i.condition()), thenJson, elseJson);
}
private static String loopToJson(LoopNode l) {
return """
{"type":"Loop","initializer":%1$s,"condition":%2$s,"update":%3$s,"body":%4$s}
""".formatted(
l.initializer() != null ? nodeToJson(l.initializer()) : "null",
l.condition() != null ? exprToJson(l.condition()) : "null",
l.update() != null ? nodeToJson(l.update()) : "null",
listToJsonArray(l.body(), ASTJsonSerializer::nodeToJson)
);
}
private static String returnToJson(ReturnNode r) {
var sb = new StringBuilder("{\"type\":\"Return\"");
r.getExpression().ifPresent(expr ->
sb.append(",\"value\":").append(exprToJson(expr))
);
sb.append("}");
return sb.toString();
}
private static String exprStmtToJson(ExpressionStatementNode e) {
return """
{"type":"ExpressionStatement","expression":%s}
""".formatted(exprToJson(e.expression()));
}
private static String exprToJson(ExpressionNode expr) {
return switch (expr) {
case BinaryExpressionNode b -> binaryToJson(b);
case IdentifierNode id -> idToJson(id);
case NumberLiteralNode n -> numToJson(n);
case StringLiteralNode s -> strToJson(s);
case CallExpressionNode c -> callToJson(c);
case MemberExpressionNode m -> memberToJson(m);
default -> simpleTypeJson(expr.getClass().getSimpleName());
};
}
private static String binaryToJson(BinaryExpressionNode b) {
return """
{"type":"BinaryExpression","left":%1$s,"operator":%2$s,"right":%3$s}
""".formatted(exprToJson(b.left()), quote(b.operator()), exprToJson(b.right()));
}
private static String idToJson(IdentifierNode id) {
return """
{"type":"Identifier","name":%s}
""".formatted(quote(id.name()));
}
private static String numToJson(NumberLiteralNode n) {
return """
{"type":"NumberLiteral","value":%s}
""".formatted(quote(n.value()));
}
private static String strToJson(StringLiteralNode s) {
return """
{"type":"StringLiteral","value":%s}
""".formatted(quote(s.value()));
}
private static String callToJson(CallExpressionNode c) {
return """
{"type":"CallExpression","callee":%1$s,"arguments":%2$s}
""".formatted(exprToJson(c.callee()), listToJsonArray(c.arguments(), ASTJsonSerializer::exprToJson));
}
private static String memberToJson(MemberExpressionNode m) {
return """
{"type":"MemberExpression","object":%1$s,"member":%2$s}
""".formatted(exprToJson(m.object()), quote(m.member()));
}
// ======== 通用辅助 ========
private static String simpleTypeJson(String typeName) {
return "{\"type\":" + quote(typeName) + "}";
}
private static <T> String listToJsonArray(List<T> list, Function<T, String> mapper) {
return list.stream()
.map(mapper)
.collect(Collectors.joining(",", "[", "]"));
}
private static String quote(String s) {
var sb = new StringBuilder("\"");
for (char c : s.toCharArray()) {
switch (c) {
case '\\' -> sb.append("\\\\");
case '\"' -> sb.append("\\\"");
case '\n' -> sb.append("\\n");
case '\r' -> sb.append("\\r");
case '\t' -> sb.append("\\t");
default -> sb.append(c);
}
} }
sb.append('"'); return m;
return sb.toString(); }
/**
* AST 根节点列表序列化为 JSON 字符串
*
* @param ast 表示抽象语法树根节点的 List<Node>
* @return 对应的 JSON 格式字符串
*/
public static String toJsonString(List<Node> ast) {
List<Object> list = new ArrayList<>(ast.size());
for (Node n : ast) {
list.add(nodeToMap(n));
}
return JSONParser.toJson(list);
}
/**
* 递归地将 AST 节点转换为 Map List 等通用结构
* 便于后续统一序列化
*/
private static Object nodeToMap(Node n) {
return switch (n) {
case ModuleNode(String name, List<ImportNode> imports, List<FunctionNode> functions) -> {
Map<String, Object> map = newNodeMap("Module");
map.put("name", name);
List<Object> imps = new ArrayList<>(imports.size());
for (ImportNode imp : imports) {
imps.add(Map.of(
"type", "Import",
"module", imp.moduleName()
));
}
map.put("imports", imps);
List<Object> funcs = new ArrayList<>(functions.size());
for (FunctionNode f : functions) {
funcs.add(nodeToMap(f));
}
map.put("functions", funcs);
yield map;
}
case FunctionNode f -> {
Map<String, Object> map = newNodeMap("Function");
map.put("name", f.name());
List<Object> params = new ArrayList<>(f.parameters().size());
for (var p : f.parameters()) {
params.add(Map.of(
"name", p.name(),
"type", p.type()
));
}
map.put("parameters", params);
map.put("returnType", f.returnType());
List<Object> body = new ArrayList<>(f.body().size());
for (Node stmt : f.body()) {
body.add(nodeToMap(stmt));
}
map.put("body", body);
yield map;
}
case DeclarationNode d -> {
Map<String, Object> map = newNodeMap("Declaration");
map.put("name", d.getName());
map.put("varType", d.getType());
map.put("initializer",
d.getInitializer().map(ASTJsonSerializer::exprToMap).orElse(null)
);
yield map;
}
case AssignmentNode a -> exprMap("Assignment",
"variable", a.variable(),
"value", exprToMap(a.value())
);
case IfNode i -> {
Map<String, Object> map = newNodeMap("If");
map.put("condition", exprToMap(i.condition()));
List<Object> thenList = new ArrayList<>(i.thenBranch().size());
for (Node stmt : i.thenBranch()) thenList.add(nodeToMap(stmt));
map.put("then", thenList);
if (!i.elseBranch().isEmpty()) {
List<Object> elseList = new ArrayList<>(i.elseBranch().size());
for (Node stmt : i.elseBranch()) elseList.add(nodeToMap(stmt));
map.put("else", elseList);
}
yield map;
}
case LoopNode l -> {
Map<String, Object> map = newNodeMap("Loop");
map.put("initializer", l.initializer() != null ? nodeToMap(l.initializer()) : null);
map.put("condition", l.condition() != null ? exprToMap(l.condition()) : null);
map.put("update", l.update() != null ? nodeToMap(l.update()) : null);
List<Object> body = new ArrayList<>(l.body().size());
for (Node stmt : l.body()) body.add(nodeToMap(stmt));
map.put("body", body);
yield map;
}
case ReturnNode r -> {
Map<String, Object> map = newNodeMap("Return");
r.getExpression().ifPresent(expr -> map.put("value", exprToMap(expr)));
yield map;
}
case ExpressionStatementNode e -> exprMap("ExpressionStatement",
"expression", exprToMap(e.expression())
);
case ExpressionNode expressionNode -> exprToMap(expressionNode);
default -> Map.of("type", n.getClass().getSimpleName());
};
}
/**
* 将表达式节点转换为 Map 表示
*/
private static Object exprToMap(ExpressionNode expr) {
return switch (expr) {
case BinaryExpressionNode(ExpressionNode left, String operator, ExpressionNode right) -> exprMap("BinaryExpression",
"left", exprToMap(left),
"operator", operator,
"right", exprToMap(right)
);
case IdentifierNode(String name) -> exprMap("Identifier", "name", name);
case NumberLiteralNode(String value) -> exprMap("NumberLiteral", "value", value);
case StringLiteralNode(String value) -> exprMap("StringLiteral", "value", value);
case CallExpressionNode(ExpressionNode callee, List<ExpressionNode> arguments) -> {
List<Object> args = new ArrayList<>(arguments.size());
for (ExpressionNode arg : arguments) args.add(exprToMap(arg));
yield exprMap("CallExpression", "callee", exprToMap(callee), "arguments", args);
}
case MemberExpressionNode(ExpressionNode object, String member) -> exprMap("MemberExpression",
"object", exprToMap(object),
"member", member
);
default -> Map.of("type", expr.getClass().getSimpleName());
};
} }
} }

View File

@ -0,0 +1,371 @@
package org.jcnc.snow.compiler.parser.util;
import java.util.*;
import java.util.Map.Entry;
/**
* JSON 工具类提供线程安全可重用的解析与序列化功能
* <p>
* - 解析将合法的 JSON 文本转换为 Java 原生对象MapListStringNumberBoolean null
* - 序列化 Java 原生对象转换为符合 JSON 标准的字符串
* <p>
* 设计要点
* 1. 使用静态方法作为唯一入口避免状态共享导致的线程安全问题
* 2. 解析器内部使用 char[] 缓冲区提高访问性能
* 3. 维护行列号信息抛出异常时能精确定位错误位置
* 4. 序列化器基于 StringBuilder预分配容量减少中间字符串创建
*/
public class JSONParser {
// 私有构造禁止外部实例化
private JSONParser() {}
/**
* JSON 文本解析为对应的 Java 对象
* @param input JSON 格式字符串
* @return 对应的 Java 原生对象
* - JSON 对象 -> Map<String, Object>
* - JSON 数组 -> List<Object>
* - JSON 字符串 -> String
* - JSON 数值 -> Long Double
* - JSON 布尔 -> Boolean
* - JSON null -> null
* @throws RuntimeException 如果遇到语法错误或多余字符异常消息中包含行列信息
*/
public static Object parse(String input) {
return new Parser(input).parseInternal();
}
/**
* Java 原生对象序列化为 JSON 字符串
* @param obj 支持的类型MapCollectionStringNumberBoolean null
* @return 符合 JSON 规范的字符串
*/
public static String toJson(Object obj) {
return Writer.write(obj);
}
// ======= 内部解析器 =======
/**
* 负责将 char[] 缓冲区中的 JSON 文本解析为 Java 对象
*/
private static class Parser {
/** 输入缓冲区 */
private final char[] buf;
/** 当前解析到的位置索引 */
private int pos;
/** 当前字符所在行号,从 1 开始 */
private int line;
/** 当前字符所在列号,从 1 开始 */
private int col;
/**
* 构造解析器初始化缓冲区和行列信息
* @param input 待解析的 JSON 文本
*/
Parser(String input) {
this.buf = input.toCharArray();
this.pos = 0;
this.line = 1;
this.col = 1;
}
/**
* 入口方法跳过空白后调用 parseValue再校验尾部无多余字符
*/
Object parseInternal() {
skipWhitespace();
Object value = parseValue();
skipWhitespace();
if (pos < buf.length) {
error("存在无法识别的多余字符");
}
return value;
}
/**
* 根据下一个字符决定解析哪种 JSON
*/
private Object parseValue() {
skipWhitespace();
if (match("null")) return null;
if (match("true")) return Boolean.TRUE;
if (match("false")) return Boolean.FALSE;
char c = currentChar();
if (c == '"') return parseString();
if (c == '{') return parseObject();
if (c == '[') return parseArray();
if (c == '-' || isDigit(c)) return parseNumber();
error("遇到意外字符 '" + c + "'");
return null; // 永不到达
}
/**
* 解析 JSON 对象返回 Map<String, Object>
*/
private Map<String, Object> parseObject() {
expect('{'); // 跳过 '{'
skipWhitespace();
Map<String, Object> map = new LinkedHashMap<>();
// 空对象 {}
if (currentChar() == '}') {
advance(); // 跳过 '}'
return map;
}
// 多成员对象解析
while (true) {
skipWhitespace();
String key = parseString(); // 解析键
skipWhitespace(); expect(':'); skipWhitespace();
Object val = parseValue(); // 解析值
map.put(key, val);
skipWhitespace();
if (currentChar() == '}') {
advance(); // 跳过 '}'
break;
}
expect(','); skipWhitespace();
}
return map;
}
/**
* 解析 JSON 数组返回 List<Object>
*/
private List<Object> parseArray() {
expect('[');
skipWhitespace();
List<Object> list = new ArrayList<>();
// 空数组 []
if (currentChar() == ']') {
advance(); // 跳过 ']'
return list;
}
// 多元素数组解析
while (true) {
skipWhitespace();
list.add(parseValue());
skipWhitespace();
if (currentChar() == ']') {
advance();
break;
}
expect(','); skipWhitespace();
}
return list;
}
/**
* 解析 JSON 字符串文字处理转义字符
*/
private String parseString() {
expect('"'); // 跳过开头 '"'
StringBuilder sb = new StringBuilder();
while (true) {
char c = currentChar();
if (c == '"') {
advance(); // 跳过结束 '"'
break;
}
if (c == '\\') {
advance(); // 跳过 '\'
c = currentChar();
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': // 解析 Unicode 转义
String hex = new String(buf, pos+1, 4);
sb.append((char) Integer.parseInt(hex, 16));
pos += 4; col += 4;
break;
default:
error("无效转义字符 '\\" + c + "'");
}
advance();
} else {
sb.append(c);
advance();
}
}
return sb.toString();
}
/**
* 解析 JSON 数值支持整数浮点及科学计数法
*/
private Number parseNumber() {
int start = pos;
if (currentChar() == '-') advance();
while (isDigit(currentChar())) advance();
if (currentChar() == '.') {
advance();
while (isDigit(currentChar())) advance();
}
if (currentChar() == 'e' || currentChar() == 'E') {
advance();
if (currentChar() == '+' || currentChar() == '-') advance();
while (isDigit(currentChar())) advance();
}
String num = new String(buf, start, pos - start);
// 判断返回 Long 还是 Double
if (num.indexOf('.') >= 0 || num.indexOf('e') >= 0 || num.indexOf('E') >= 0) {
return Double.parseDouble(num);
}
try {
return Long.parseLong(num);
} catch (NumberFormatException e) {
return Double.parseDouble(num);
}
}
/**
* 跳过所有空白字符支持空格制表符回车换行
*/
private void skipWhitespace() {
while (pos < buf.length) {
char c = buf[pos];
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
advance();
} else {
break;
}
}
}
/**
* 获取当前位置字符超出范围返回 '\0'
*/
private char currentChar() {
return pos < buf.length ? buf[pos] : '\0';
}
/**
* 推进到下一个字符并更新行列信息
*/
private void advance() {
if (pos < buf.length) {
if (buf[pos] == '\n') {
line++; col = 1;
} else {
col++;
}
pos++;
}
}
/**
* 验证当前位置字符等于预期字符否则抛出错误
*/
private void expect(char c) {
if (currentChar() != c) {
error("期望 '" + c + "',但遇到 '" + currentChar() + "'");
}
advance();
}
/**
* 尝试匹配给定字符串匹配成功则移动位置并返回 true
*/
private boolean match(String s) {
int len = s.length();
if (pos + len > buf.length) return false;
for (int i = 0; i < len; i++) {
if (buf[pos + i] != s.charAt(i)) return false;
}
for (int i = 0; i < len; i++) advance();
return true;
}
/**
* 判断字符是否为数字
*/
private boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
/**
* 抛出带行列定位的解析错误
*/
private void error(String msg) {
throw new RuntimeException("Error at line " + line + ", column " + col + ": " + msg);
}
}
// ======= 内部序列化器 =======
/**
* 负责高效地将 Java 对象写为 JSON 文本
*/
private static class Writer {
/** 默认 StringBuilder 初始容量,避免频繁扩容 */
private static final int DEFAULT_CAPACITY = 1024;
/**
* 入口方法根据 obj 类型分派写入逻辑
*/
static String write(Object obj) {
StringBuilder sb = new StringBuilder(DEFAULT_CAPACITY);
writeValue(obj, sb);
return sb.toString();
}
/**
* 根据对象类型选择合适的写入方式
*/
@SuppressWarnings("unchecked")
private static void writeValue(Object obj, StringBuilder sb) {
if (obj == null) {
sb.append("null");
} else if (obj instanceof String) {
quote((String) obj, sb);
} else if (obj instanceof Number || obj instanceof Boolean) {
sb.append(obj.toString());
} else if (obj instanceof Map) {
sb.append('{');
boolean first = true;
for (Entry<?, ?> e : ((Map<?, ?>) obj).entrySet()) {
if (!first) sb.append(',');
first = false;
quote(e.getKey().toString(), sb);
sb.append(':');
writeValue(e.getValue(), sb);
}
sb.append('}');
} else if (obj instanceof Collection) {
sb.append('[');
boolean first = true;
for (Object item : (Collection<?>) obj) {
if (!first) sb.append(',');
first = false;
writeValue(item, sb);
}
sb.append(']');
} else {
// 其他类型使用 toString 并加引号
quote(obj.toString(), sb);
}
}
/**
* 为字符串添加双引号并转义必须的字符
*/
private static void quote(String s, StringBuilder sb) {
sb.append('"');
for (char c : s.toCharArray()) {
switch (c) {
case '\\': sb.append("\\\\"); break;
case '"': sb.append("\\\""); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
default: sb.append(c);
}
}
sb.append('"');
}
}
}

View File

@ -37,28 +37,24 @@ public class JsonFormatter {
sb.append(c); sb.append(c);
} else if (!inQuotes) { } else if (!inQuotes) {
switch (c) { switch (c) {
case '{', '[': case '{', '[' -> {
sb.append(c).append('\n'); sb.append(c).append('\n');
indent++; indent++;
appendIndent(sb, indent); appendIndent(sb, indent);
break; }
case '}', ']': case '}', ']' -> {
sb.append('\n'); sb.append('\n');
indent--; indent--;
appendIndent(sb, indent); appendIndent(sb, indent);
sb.append(c); sb.append(c);
break; }
case ',': case ',' -> sb.append(c).append('\n').append(" ".repeat(indent));
sb.append(c).append('\n'); case ':' -> sb.append(c).append(' ');
appendIndent(sb, indent); default -> {
break;
case ':':
sb.append(c).append(' ');
break;
default:
if (!Character.isWhitespace(c)) { if (!Character.isWhitespace(c)) {
sb.append(c); sb.append(c);
} }
}
} }
} else { } else {
// 字符串内部原样输出 // 字符串内部原样输出

10
test
View File

@ -1,15 +1,11 @@
module: CommonTasks module: CommonTasks
function: add_numbers function: add_numbers
body: body:
return num1 + num2 return num1 + num2
end body end body
parameter: parameter:
declare num1: int declare num1: int
declare num2: int declare num2: int
return_type: int return_type: int
end function end function
@ -41,7 +37,6 @@ end module
module: MainModule module: MainModule
import: CommonTasks, MathUtils, StringUtils, BuiltinUtils import: CommonTasks, MathUtils, StringUtils, BuiltinUtils
function: test function: test
parameter: parameter:
declare first_str: string declare first_str: string
@ -52,7 +47,6 @@ module: MainModule
end body end body
end function end function
function: main function: main
parameter: parameter:
declare args: string declare args: string
@ -89,9 +83,9 @@ module: MainModule
BuiltinUtils.print(" first inner") BuiltinUtils.print(" first inner")
else else
if j % 2 == 0 then if j % 2 == 0 then
BuiltinUtils.print(" j even") BuiltinUtils.print("j even")
else else
BuiltinUtils.print(" j odd") BuiltinUtils.print("j odd")
end if end if
end if end if
end body end body