From f1086a1ef925b898bf413fff1403adff881e3859 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 3 Jul 2025 23:49:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=9F=E4=B8=80=20parser=20=E7=9A=84?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../compiler/parser/context/MissingToken.java | 11 ++ .../parser/context/ParseException.java | 21 ++- .../compiler/parser/context/TokenStream.java | 10 +- .../parser/context/UnexpectedToken.java | 11 ++ .../parser/context/UnsupportedFeature.java | 11 ++ .../compiler/parser/core/ParserEngine.java | 59 ++++++-- .../expression/PrattExpressionParser.java | 8 +- .../compiler/parser/module/ModuleParser.java | 5 +- .../statement/ExpressionStatementParser.java | 5 +- .../parser/top/ScriptTopLevelParser.java | 3 +- .../parser/utils/FlexibleSectionParser.java | 5 +- .../compiler/parser/utils/JSONParser.java | 131 ++++++++++++------ 12 files changed, 205 insertions(+), 75 deletions(-) create mode 100644 src/main/java/org/jcnc/snow/compiler/parser/context/MissingToken.java create mode 100644 src/main/java/org/jcnc/snow/compiler/parser/context/UnexpectedToken.java create mode 100644 src/main/java/org/jcnc/snow/compiler/parser/context/UnsupportedFeature.java diff --git a/src/main/java/org/jcnc/snow/compiler/parser/context/MissingToken.java b/src/main/java/org/jcnc/snow/compiler/parser/context/MissingToken.java new file mode 100644 index 0000000..b56ae02 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/context/MissingToken.java @@ -0,0 +1,11 @@ +package org.jcnc.snow.compiler.parser.context; + +/** + * 当语法结构缺失必须出现的 Token 时抛出。 + */ +public final class MissingToken extends ParseException { + + public MissingToken(String message) { + super(message); + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/context/ParseException.java b/src/main/java/org/jcnc/snow/compiler/parser/context/ParseException.java index 0313d7a..0262868 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/context/ParseException.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/context/ParseException.java @@ -1,22 +1,19 @@ package org.jcnc.snow.compiler.parser.context; /** - * {@code ParseException} 表示语法分析阶段发生的错误。 - *

- * 当语法分析器遇到非法的语法结构或无法继续处理的标记序列时, - * 应抛出该异常以中断当前解析流程,并向调用方报告错误信息。 - *

- *

- * 该异常通常由 {@code ParserContext} 或各类语法规则处理器主动抛出, - * 用于提示编译器前端或 IDE 系统进行错误提示与恢复。 - *

+ * {@code ParseException}——语法分析阶段所有错误的基类。 + * + *

声明为 sealed,仅允许 {@link UnexpectedToken}、 + * {@link MissingToken}、{@link UnsupportedFeature} 三个受信子类继承, + * 以便调用方根据异常类型进行精确处理。

*/ -public class ParseException extends RuntimeException { +public sealed class ParseException extends RuntimeException + permits UnexpectedToken, MissingToken, UnsupportedFeature { /** - * 构造一个带有错误描述信息的解析异常实例。 + * 构造解析异常并附带错误消息。 * - * @param message 错误描述文本,用于指明具体的语法错误原因 + * @param message 错误描述 */ public ParseException(String message) { super(message); diff --git a/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java b/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java index 0f28737..9169318 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/context/TokenStream.java @@ -31,7 +31,7 @@ public class TokenStream { */ public TokenStream(List tokens) { if (tokens == null) { - throw new NullPointerException("Token list cannot be null."); + throw new NullPointerException("Token 列表不能为空"); } this.tokens = tokens; } @@ -103,8 +103,8 @@ public class TokenStream { Token t = peek(); if (!t.getLexeme().equals(lexeme)) { throw new ParseException( - "Expected lexeme '" + lexeme + "' but got '" + t.getLexeme() + - "' at " + t.getLine() + ":" + t.getCol() + "期望的词素是'" + lexeme + "',但得到的是'" + t.getLexeme() + + "在" + t.getLine() + ":" + t.getCol() ); } return next(); @@ -122,8 +122,8 @@ public class TokenStream { Token t = peek(); if (t.getType() != type) { throw new ParseException( - "Expected token type " + type + " but got " + t.getType() + - " ('" + t.getLexeme() + "') at " + t.getLine() + ":" + t.getCol() + "期望的标记类型为 " + type + " 但实际得到的是 " + t.getType() + + " ('" + t.getLexeme() + "') 在 " + t.getLine() + ":" + t.getCol() ); } return next(); diff --git a/src/main/java/org/jcnc/snow/compiler/parser/context/UnexpectedToken.java b/src/main/java/org/jcnc/snow/compiler/parser/context/UnexpectedToken.java new file mode 100644 index 0000000..bfa5ecd --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/context/UnexpectedToken.java @@ -0,0 +1,11 @@ +package org.jcnc.snow.compiler.parser.context; + +/** + * 当解析过程中遇到意料之外或无法识别的 Token 时抛出。 + */ +public final class UnexpectedToken extends ParseException { + + public UnexpectedToken(String message) { + super(message); + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/context/UnsupportedFeature.java b/src/main/java/org/jcnc/snow/compiler/parser/context/UnsupportedFeature.java new file mode 100644 index 0000000..558f32a --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/context/UnsupportedFeature.java @@ -0,0 +1,11 @@ +package org.jcnc.snow.compiler.parser.context; + +/** + * 当源码使用了当前编译器尚未支持的语言特性或语法时抛出。 + */ +public final class UnsupportedFeature extends ParseException { + + public UnsupportedFeature(String message) { + super(message); + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java b/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java index 7d0854f..c5bfb36 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java @@ -1,24 +1,58 @@ package org.jcnc.snow.compiler.parser.core; import org.jcnc.snow.compiler.lexer.token.TokenType; +import org.jcnc.snow.compiler.parser.ast.base.Node; import org.jcnc.snow.compiler.parser.base.TopLevelParser; import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.context.TokenStream; +import org.jcnc.snow.compiler.parser.context.UnexpectedToken; import org.jcnc.snow.compiler.parser.factory.TopLevelParserFactory; -import org.jcnc.snow.compiler.parser.ast.base.Node; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; +/** + * 语法解析引擎(ParserEngine)。 + *

+ * 负责驱动 Snow 源码的顶层语法结构解析,将源码 TokenStream + * 递交给各类 TopLevelParser,并收集语法树节点与异常。 + * 支持容错解析,能够批量报告所有语法错误,并提供同步恢复功能。 + *

+ * + *

+ * 典型用法: + *

+ *     ParserEngine engine = new ParserEngine(context);
+ *     List<Node> ast = engine.parse();
+ * 
+ *

+ * + * @param ctx 解析器上下文,负责持有 TokenStream 及所有全局状态。 + */ public record ParserEngine(ParserContext ctx) { + /** + * 解析输入 TokenStream,生成语法树节点列表。 + * + *

+ * 调用各类顶级语句解析器(如 module, func, import), + * 遇到错误时会自动跳过到下一行或已知结构关键字,继续后续分析, + * 最终汇总所有错误。如果解析出现错误,将以 + * {@link UnexpectedToken} 抛出所有语法错误信息。 + *

+ * + * @return AST 节点列表,每个节点对应一个顶层语法结构 + * @throws UnexpectedToken 如果解析期间发现语法错误 + */ public List parse() { List nodes = new ArrayList<>(); List errs = new ArrayList<>(); TokenStream ts = ctx.getTokens(); + // 主循环:直到全部 token 处理完毕 while (ts.isAtEnd()) { - // 跳过空行 + // 跳过所有空行 if (ts.peek().getType() == TokenType.NEWLINE) { ts.next(); continue; @@ -30,22 +64,31 @@ public record ParserEngine(ParserContext ctx) { nodes.add(parser.parse(ctx)); } catch (Exception ex) { errs.add(ex.getMessage()); - synchronize(ts); // 错误恢复 + synchronize(ts); // 错误恢复:同步到下一个语句 } } + // 批量报告所有解析错误 if (!errs.isEmpty()) { - throw new IllegalStateException("解析过程中检测到 " - + errs.size() + " 处错误:\n - " - + String.join("\n - ", errs)); + StringJoiner sj = new StringJoiner("\n - ", "", ""); + errs.forEach(sj::add); + throw new UnexpectedToken("解析过程中检测到 " + + errs.size() + " 处错误:\n - " + sj); } return nodes; } /** - * 错误同步:跳到下一行或下一个已注册顶层关键字 + * 错误同步机制:跳过当前 TokenStream,直到遇到下一行 + * 或下一个可识别的顶级结构关键字,以保证后续解析不会被卡住。 + *

+ * 同时会跳过连续空行。 + *

+ * + * @param ts 当前 TokenStream */ private void synchronize(TokenStream ts) { + // 跳到下一行或下一个顶层结构关键字 while (ts.isAtEnd()) { if (ts.peek().getType() == TokenType.NEWLINE) { ts.next(); @@ -56,7 +99,7 @@ public record ParserEngine(ParserContext ctx) { } ts.next(); } - // 连续空行全部吃掉 + // 吃掉后续所有空行 while (ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) { ts.next(); } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java index 4f79274..c0a869e 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java @@ -4,6 +4,7 @@ import org.jcnc.snow.compiler.lexer.token.Token; import org.jcnc.snow.compiler.lexer.token.TokenType; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; import org.jcnc.snow.compiler.parser.context.ParserContext; +import org.jcnc.snow.compiler.parser.context.UnsupportedFeature; import org.jcnc.snow.compiler.parser.expression.base.ExpressionParser; import org.jcnc.snow.compiler.parser.expression.base.InfixParselet; import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet; @@ -87,7 +88,7 @@ public class PrattExpressionParser implements ExpressionParser { Token token = ctx.getTokens().next(); PrefixParselet prefix = prefixes.get(token.getType().name()); if (prefix == null) { - throw new IllegalStateException("没有为该 Token 类型注册前缀解析器: " + token.getType()); + throw new UnsupportedFeature("没有为该 Token 类型注册前缀解析器: " + token.getType()); } ExpressionNode left = prefix.parse(ctx, token); @@ -96,7 +97,10 @@ public class PrattExpressionParser implements ExpressionParser { && prec.ordinal() < nextPrecedence(ctx)) { String lex = ctx.getTokens().peek().getLexeme(); InfixParselet infix = infixes.get(lex); - if (infix == null) break; + if (infix == null) { + throw new UnsupportedFeature( + "没有为该 Token 类型注册中缀解析器: " + token.getType()); + } left = infix.parse(ctx, left); } return left; diff --git a/src/main/java/org/jcnc/snow/compiler/parser/module/ModuleParser.java b/src/main/java/org/jcnc/snow/compiler/parser/module/ModuleParser.java index ac9e05d..bcf2555 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/module/ModuleParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/module/ModuleParser.java @@ -7,6 +7,7 @@ import org.jcnc.snow.compiler.parser.context.TokenStream; import org.jcnc.snow.compiler.parser.ast.ImportNode; import org.jcnc.snow.compiler.parser.ast.ModuleNode; import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.parser.context.UnexpectedToken; import org.jcnc.snow.compiler.parser.function.FunctionParser; import java.util.ArrayList; @@ -33,7 +34,7 @@ public class ModuleParser implements TopLevelParser { * * @param ctx 当前解析器上下文,包含词法流、状态信息等。 * @return 返回一个 {@link ModuleNode} 实例,表示完整模块的语法结构。 - * @throws IllegalStateException 当模块体中出现未识别的语句时抛出。 + * @throws UnexpectedToken 当模块体中出现未识别的语句时抛出。 */ @Override public ModuleNode parse(ParserContext ctx) { @@ -86,7 +87,7 @@ public class ModuleParser implements TopLevelParser { functions.add(funcParser.parse(ctx)); } else { // 遇到无法识别的语句开头,抛出异常并提供详细提示 - throw new IllegalStateException("Unexpected token in module: " + lex); + throw new UnexpectedToken("Unexpected token in module: " + lex); } } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/statement/ExpressionStatementParser.java b/src/main/java/org/jcnc/snow/compiler/parser/statement/ExpressionStatementParser.java index 526dbf0..3c14a90 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/statement/ExpressionStatementParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/statement/ExpressionStatementParser.java @@ -7,6 +7,7 @@ import org.jcnc.snow.compiler.parser.ast.ExpressionStatementNode; import org.jcnc.snow.compiler.parser.ast.base.StatementNode; import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.context.TokenStream; +import org.jcnc.snow.compiler.parser.context.UnexpectedToken; import org.jcnc.snow.compiler.parser.expression.PrattExpressionParser; /** @@ -39,7 +40,7 @@ public class ExpressionStatementParser implements StatementParser { * * @param ctx 当前解析上下文,提供词法流与状态信息。 * @return 返回 {@link AssignmentNode} 或 {@link ExpressionStatementNode} 表示的语法节点。 - * @throws IllegalStateException 若表达式起始为关键字或语法非法。 + * @throws UnexpectedToken 若表达式起始为关键字或语法非法。 */ @Override public StatementNode parse(ParserContext ctx) { @@ -47,7 +48,7 @@ public class ExpressionStatementParser implements StatementParser { // 快速检查:若遇空行或关键字开头,不可作为表达式语句 if (ts.peek().getType() == TokenType.NEWLINE || ts.peek().getType() == TokenType.KEYWORD) { - throw new IllegalStateException("Cannot parse expression starting with keyword: " + ts.peek().getLexeme()); + throw new UnexpectedToken("无法解析以关键字开头的表达式: " + ts.peek().getLexeme()); } // 获取当前 token 的行号、列号和文件名 diff --git a/src/main/java/org/jcnc/snow/compiler/parser/top/ScriptTopLevelParser.java b/src/main/java/org/jcnc/snow/compiler/parser/top/ScriptTopLevelParser.java index f669c8e..3403dcf 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/top/ScriptTopLevelParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/top/ScriptTopLevelParser.java @@ -19,7 +19,6 @@ public class ScriptTopLevelParser implements TopLevelParser { public Node parse(ParserContext ctx) { String first = ctx.getTokens().peek().getLexeme(); StatementParser sp = StatementParserFactory.get(first); - StatementNode stmt = sp.parse(ctx); - return stmt; // StatementNode 亦是 Node + return sp.parse(ctx); } } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/utils/FlexibleSectionParser.java b/src/main/java/org/jcnc/snow/compiler/parser/utils/FlexibleSectionParser.java index c319d4f..90cbc2c 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/utils/FlexibleSectionParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/utils/FlexibleSectionParser.java @@ -3,6 +3,7 @@ package org.jcnc.snow.compiler.parser.utils; import org.jcnc.snow.compiler.lexer.token.TokenType; import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.context.TokenStream; +import org.jcnc.snow.compiler.parser.context.UnexpectedToken; import java.util.Map; import java.util.function.BiConsumer; @@ -45,7 +46,7 @@ public class FlexibleSectionParser { * @param ctx 当前解析上下文,提供语法环境与作用域信息 * @param tokens 当前 token 流 * @param sectionDefinitions 各个区块的定义映射(key 为关键字,value 为判断 + 解析逻辑组合) - * @throws RuntimeException 若出现无法识别的关键字或未满足的匹配条件 + * @throws UnexpectedToken 若出现无法识别的关键字或未满足的匹配条件 */ public static void parse(ParserContext ctx, TokenStream tokens, @@ -70,7 +71,7 @@ public class FlexibleSectionParser { if (definition != null && definition.condition().test(tokens)) { definition.parser().accept(ctx, tokens); // 执行解析逻辑 } else { - throw new RuntimeException("未识别的关键字或条件不满足: " + keyword); + throw new UnexpectedToken("未识别的关键字或条件不满足: " + keyword); } } } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/utils/JSONParser.java b/src/main/java/org/jcnc/snow/compiler/parser/utils/JSONParser.java index ce30193..6d77478 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/utils/JSONParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/utils/JSONParser.java @@ -1,5 +1,7 @@ package org.jcnc.snow.compiler.parser.utils; +import org.jcnc.snow.compiler.parser.context.UnexpectedToken; + import java.util.*; import java.util.Map.Entry; @@ -10,26 +12,28 @@ import java.util.Map.Entry; * - 序列化:将 Java 原生对象转换为符合 JSON 标准的字符串 *

* 设计要点: - * 1. 使用静态方法作为唯一入口,避免状态共享导致的线程安全问题 - * 2. 解析器内部使用 char[] 缓冲区,提高访问性能 - * 3. 维护行列号信息,抛出异常时能精确定位错误位置 - * 4. 序列化器基于 StringBuilder,预分配容量,减少中间字符串创建 + * 1. 使用静态方法作为唯一入口,避免状态共享导致的线程安全问题 + * 2. 解析器内部使用 char[] 缓冲区,提高访问性能 + * 3. 维护行列号信息,抛出异常时能精确定位错误位置 + * 4. 序列化器基于 StringBuilder,预分配容量,减少中间字符串创建 */ public class JSONParser { - private JSONParser() {} + private JSONParser() { + } /** * 将 JSON 文本解析为对应的 Java 对象 + * * @param input JSON 格式字符串 * @return 对应的 Java 原生对象: - * - JSON 对象 -> Map - * - JSON 数组 -> List - * - JSON 字符串 -> String - * - JSON 数值 -> Long 或 Double - * - JSON 布尔 -> Boolean - * - JSON null -> null - * @throws RuntimeException 如果遇到语法错误或多余字符,异常消息中包含行列信息 + * - JSON 对象 -> Map + * - JSON 数组 -> List + * - JSON 字符串 -> String + * - JSON 数值 -> Long 或 Double + * - JSON 布尔 -> Boolean + * - JSON null -> null + * @throws UnexpectedToken 如果遇到语法错误或多余字符,异常消息中包含行列信息 */ public static Object parse(String input) { return new Parser(input).parseInternal(); @@ -37,6 +41,7 @@ public class JSONParser { /** * 将 Java 原生对象序列化为 JSON 字符串 + * * @param obj 支持的类型:Map、Collection、String、Number、Boolean 或 null * @return 符合 JSON 规范的字符串 */ @@ -45,21 +50,31 @@ public class JSONParser { } // ======= 内部解析器 ======= + /** * 负责将 char[] 缓冲区中的 JSON 文本解析为 Java 对象 */ private static class Parser { - /** 输入缓冲区 */ + /** + * 输入缓冲区 + */ private final char[] buf; - /** 当前解析到的位置索引 */ + /** + * 当前解析到的位置索引 + */ private int pos; - /** 当前字符所在行号,从 1 开始 */ + /** + * 当前字符所在行号,从 1 开始 + */ private int line; - /** 当前字符所在列号,从 1 开始 */ + /** + * 当前字符所在列号,从 1 开始 + */ private int col; /** * 构造解析器,初始化缓冲区和行列信息 + * * @param input 待解析的 JSON 文本 */ Parser(String input) { @@ -115,7 +130,9 @@ public class JSONParser { while (true) { skipWhitespace(); String key = parseString(); // 解析键 - skipWhitespace(); expect(':'); skipWhitespace(); + skipWhitespace(); + expect(':'); + skipWhitespace(); Object val = parseValue(); // 解析值 map.put(key, val); skipWhitespace(); @@ -123,7 +140,8 @@ public class JSONParser { advance(); // 跳过 '}' break; } - expect(','); skipWhitespace(); + expect(','); + skipWhitespace(); } return map; } @@ -149,7 +167,8 @@ public class JSONParser { advance(); break; } - expect(','); skipWhitespace(); + expect(','); + skipWhitespace(); } return list; } @@ -170,18 +189,35 @@ public class JSONParser { 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 '"': + 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); + String hex = new String(buf, pos + 1, 4); sb.append((char) Integer.parseInt(hex, 16)); - pos += 4; col += 4; + pos += 4; + col += 4; break; default: error("无效转义字符 '\\" + c + "'"); @@ -250,7 +286,8 @@ public class JSONParser { private void advance() { if (pos < buf.length) { if (buf[pos] == '\n') { - line++; col = 1; + line++; + col = 1; } else { col++; } @@ -292,16 +329,19 @@ public class JSONParser { * 抛出带行列定位的解析错误 */ private void error(String msg) { - throw new RuntimeException("Error at line " + line + ", column " + col + ": " + msg); + throw new UnexpectedToken("在第 " + line + " 行,第 " + col + " 列出现错误: " + msg); } } // ======= 内部序列化器 ======= + /** * 负责高效地将 Java 对象写为 JSON 文本 */ private static class Writer { - /** 默认 StringBuilder 初始容量,避免频繁扩容 */ + /** + * 默认 StringBuilder 初始容量,避免频繁扩容 + */ private static final int DEFAULT_CAPACITY = 1024; /** @@ -344,8 +384,8 @@ public class JSONParser { } sb.append(']'); } else { - // 其他类型,使用 toString 并加引号 - quote(obj.toString(), sb); + throw new UnsupportedOperationException( + "不支持的 JSON 字符串化类型: " + obj.getClass()); } } @@ -356,12 +396,23 @@ public class JSONParser { 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); + 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('"');