refactor: 重构语法分析模块并优化错误处理机制

- 优化了 ExpressionStatementParser、FlexibleSectionParser 和 JSONParser 的代码结构
- 改进了模块解析器 (ModuleParser) 的实现
- 重构了语法异常 (ParseException) 类,增加了错误位置信息
- 新增 ParseError 类用于收集和展示语法错误信息
- 改进了同步机制以更好地恢复解析过程
This commit is contained in:
Luke 2025-07-05 17:02:45 +08:00
parent e33f6b0ce2
commit e11d519627
14 changed files with 457 additions and 381 deletions

View File

@ -1,7 +1,7 @@
function: main
return_type: int
return_type: int 111
body:
declare num1 :int = 3.1 G
return 65537
end body
end function

View File

@ -91,7 +91,7 @@ public class SnowCLI {
System.exit(exitCode);
} catch (Exception e) {
// 捕获命令执行过程中的异常并打印错误消息
// System.err.println("Error: " + e.getMessage());
System.err.println(e.getMessage());
System.exit(1);
}
}

View File

@ -1,11 +1,22 @@
package org.jcnc.snow.compiler.parser.context;
/**
* 当语法结构缺失必须出现的 Token 时抛出
* 表示在语法分析过程中必须出现的 Token 缺失时抛出的异常
* <p>
* 当分析器检测到输入流中缺少某个预期 Token 会抛出此异常以便准确地指明语法错误位置
* 该异常包含了缺失 Token 的名称以及发生缺失的位置行号和列号便于错误定位和后续处理
* </p>
*/
public final class MissingToken extends ParseException {
public MissingToken(String message) {
super(message);
/**
* 构造一个表示缺失 Token 的异常
*
* @param expected 预期但未出现的 Token 名称
* @param line 发生异常的行号
* @param column 发生异常的列号
*/
public MissingToken(String expected, int line, int column) {
super("缺失 Token: " + expected, line, column);
}
}

View File

@ -0,0 +1,45 @@
package org.jcnc.snow.compiler.parser.context;
/**
* 语法错误的数据传输对象DTO
* <p>
* 用于收集和展示语法分析过程中检测到的错误信息便于错误定位和报告
* 包含出错文件行号列号和具体错误信息等字段
* </p>
*/
public class ParseError {
/** 出错的文件名 */
private final String file;
/** 出错的行号 */
private final int line;
/** 出错的列号 */
private final int column;
/** 错误信息描述 */
private final String message;
/**
* 构造一个语法错误数据对象
*
* @param file 出错文件名
* @param line 出错行号
* @param column 出错列号
* @param message 错误信息描述
*/
public ParseError(String file, int line, int column, String message) {
this.file = file;
this.line = line;
this.column = column;
this.message = message;
}
/**
* 返回该错误对象的字符串表示
*
* @return 格式化后的错误描述字符串
*/
@Override
public String toString() {
return file + ": 行 " + line + ", 列 " + column + ": " + message;
}
}

View File

@ -1,21 +1,75 @@
package org.jcnc.snow.compiler.parser.context;
/**
* {@code ParseException}语法分析阶段所有错误的基类
* 语法分析阶段所有错误的基类
* <p>
* 本异常作为语法分析相关错误的统一父类屏蔽了堆栈信息确保在命令行界面CLI输出时只占用一行方便用户快速定位问题
* 通过 {@code permits} 关键字限定了可被继承的异常类型增强类型安全性
* </p>
*
* <p>声明为 <em>sealed</em>仅允许 {@link UnexpectedToken}
* {@link MissingToken}{@link UnsupportedFeature} 三个受信子类继承
* 以便调用方根据异常类型进行精确处理</p>
* <p>
* 该异常携带错误发生的行号列号和具体原因信息用于语法错误的精确报告和输出展示
* </p>
*/
public sealed class ParseException extends RuntimeException
permits UnexpectedToken, MissingToken, UnsupportedFeature {
permits MissingToken, UnexpectedToken, UnsupportedFeature {
/** 出错行号(从 1 开始) */
private final int line;
/** 出错列号(从 1 开始) */
private final int column;
/** 错误原因描述 */
private final String reason;
/**
* 构造解析异常并附带错误消息
* 构造语法分析异常
*
* @param message 错误描述
* @param reason 错误原因描述
* @param line 出错行号 1 开始
* @param column 出错列号 1 开始
*/
public ParseException(String message) {
super(message);
public ParseException(String reason, int line, int column) {
// 禁用 cause / suppression / stackTrace确保 CLI 输出简洁
super(reason, null, false, false);
this.reason = reason;
this.line = line;
this.column = column;
}
/**
* 禁用堆栈信息的生成保证异常始终为单行输出
*
* @return 当前异常对象自身
*/
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
/**
* 获取出错行号 1 开始
*
* @return 行号
*/
public int getLine() {
return line;
}
/**
* 获取出错列号 1 开始
*
* @return 列号
*/
public int getColumn() {
return column;
}
/**
* 获取错误原因描述
*
* @return 错误原因
*/
public String getReason() {
return reason;
}
}

View File

@ -6,27 +6,29 @@ import org.jcnc.snow.compiler.lexer.token.TokenType;
import java.util.List;
/**
* {@code TokenStream} 封装了一个 Token 列表并维护当前解析位置是语法分析器读取词法单元的核心工具类
*
* <p>提供前瞻peek消费next匹配match断言expect等常用操作
* 支持前向查看和异常处理适用于递归下降解析等常见语法构建策略</p>
* {@code TokenStream} 封装了 Token 序列并维护当前解析位置是语法分析器读取词法单元的核心工具类
* <p>
* 该类提供前瞻peek消费next匹配match断言expect等常用操作
* 支持前向查看和异常处理适用于递归下降等常见语法解析策略
* 设计上自动跳过注释COMMENTtoken并对越界情况提供自动构造的 EOF文件结束token
* 有效提升语法处理的健壮性与易用性
* </p>
*/
public class TokenStream {
/**
* Token 列表
* Token 列表
*/
private final List<Token> tokens;
/**
* 当前解析位置索引
* 当前解析位置索引
*/
private int pos = 0;
/**
* 使用 Token 列表构造 TokenStream
*
* @param tokens 由词法分析器产生 Token 集合
* @param tokens 词法分析器输出 Token 集合
* @throws NullPointerException 如果 tokens null
*/
public TokenStream(List<Token> tokens) {
@ -37,14 +39,13 @@ public class TokenStream {
}
/**
* 向前查看指定偏移量处的 Token不移动位置
* offset==0 时自动跳过当前位置的所有注释COMMENTtoken
* 向前查看指定偏移量处的 Token不移动当前位置
* {@code offset == 0} 时自动跳过所有连续的注释COMMENTtoken
*
* @param offset 相对当前位置的偏移量0 表示当前 token
* @param offset 相对当前位置的偏移量0 表示当前位置 token
* @return 指定位置的 Token若越界则返回自动构造的 EOF Token
*/
public Token peek(int offset) {
// 只在 offset==0 时跳注释向前多步 peek 由调用方控制
if (offset == 0) {
skipTrivia();
}
@ -56,9 +57,9 @@ public class TokenStream {
}
/**
* 查看当前位置的 Token等效于 {@code peek(0)}
* 查看当前位置的有效 Token已跳过注释
*
* @return 当前有效 Token已跳过注释
* @return 当前 Token等效于 {@code peek(0)}
*/
public Token peek() {
skipTrivia();
@ -66,21 +67,21 @@ public class TokenStream {
}
/**
* 消费当前位置的 Token 并返回位置前移注释 token 会被自动跳过
* 消费当前位置的有效 Token 并前移指针自动跳过注释 token
*
* @return 被消费的有效 Token已跳过注释
* @return 被消费的有效 Token
*/
public Token next() {
Token t = peek(); // peek() 已跳过注释
pos++; // 指针指向下一个 raw token
skipTrivia(); // 立即吞掉紧随其后的注释若有
Token t = peek();
pos++;
skipTrivia();
return t;
}
/**
* 匹配当前 Token 的词素与指定字符串若匹配则消费该 token 并前移指针
* 若当前 Token 的词素等于指定字符串则消费该 Token 并前移否则不变
*
* @param lexeme 待匹配的词素字符串
* @param lexeme 目标词素字符串
* @return 匹配成功返回 true否则返回 false
*/
public boolean match(String lexeme) {
@ -92,75 +93,60 @@ public class TokenStream {
}
/**
* 断言当前 Token 的词素与指定值相符否则抛出 {@link ParseException}
* 匹配成功会消费该 token 并前移指针
* 断言当前位置 Token 的词素等于指定值否则抛出 {@link ParseException}
* 匹配成功时消费该 Token 并前移
*
* @param lexeme 期望的词素
* @param lexeme 期望的词素字符串
* @return 匹配成功的 Token
* @throws ParseException 若词素不
* @throws ParseException 若词素不匹配
*/
public Token expect(String lexeme) {
Token t = peek();
if (!t.getLexeme().equals(lexeme)) {
throw new ParseException(
"期望的词素是'" + lexeme + "',但得到的是'" + t.getLexeme() +
"" + t.getLine() + ":" + t.getCol()
"期望的词素是 '" + lexeme + "',但得到的是 '" + t.getLexeme() + "'",
t.getLine(), t.getCol()
);
}
return next();
}
/**
* 断言当前 Token 类型为指定类型否则抛出 {@link ParseException}
* 匹配成功会消费该 token 并前移指针
* 断言当前位置 Token 类型为指定类型否则抛出 {@link ParseException}
* 匹配成功时消费该 Token 并前移
*
* @param type 期望的 Token 类型
* @return 匹配成功的 Token
* @throws ParseException 若类型不匹配
* @throws ParseException 若类型不
*/
public Token expectType(TokenType type) {
Token t = peek();
if (t.getType() != type) {
throw new ParseException(
"期望的标记类型为 " + type + " 但实际得到的是 " + t.getType() +
" ('" + t.getLexeme() + "') 在 " + t.getLine() + ":" + t.getCol()
"期望的标记类型为 " + type + ",但实际得到的是 " + t.getType() +
" ('" + t.getLexeme() + "')",
t.getLine(), t.getCol()
);
}
return next();
}
/**
* 判断是否到达文件末尾EOF
* 判断是否已到达文件末尾EOF
*
* @return 若当前位置 Token EOF则返回 true否则返回 false
* @return 若当前位置 Token EOF则返回 true否则返回 false
*/
public boolean isAtEnd() {
return peek().getType() != TokenType.EOF;
return peek().getType() == TokenType.EOF;
}
/**
* 跳过所有连续的注释COMMENTtoken
*
* <p>
* 此方法会检查当前指针 <code>pos</code> 所指向的 token
* 如果其类型为 <code>TokenType.COMMENT</code>则直接将指针递增
* 直到遇到非 COMMENT 类型或到达 token 列表末尾
* </p>
*
* <p>
* 注意此方法<strong>只会跳过注释</strong>不会递归或调用任何
* 会产生递归的方法 peek()/next()以避免堆栈溢出
* </p>
*
* <p>
* 使用场景词法分析产物中允许出现注释 token语法分析时需要自动跳过它们
* 保证 parser 只处理有效语法 token
* </p>
* 跳过所有连续的注释COMMENTtoken使解析器总是定位在第一个有效 Token
*/
private void skipTrivia() {
while (pos < tokens.size()
&& tokens.get(pos).getType() == TokenType.COMMENT) {
pos++; // 直接跳过 COMMENT 类型
pos++;
}
}
}

View File

@ -1,11 +1,21 @@
package org.jcnc.snow.compiler.parser.context;
/**
* 当解析过程中遇到意料之外或无法识别的 Token 时抛出
* 表示在语法分析过程中遇到意料之外或无法识别的 Token 时抛出的异常
* <p>
* 当分析器检测到实际遇到的 Token 不符合语法规则或与预期类型不符时会抛出本异常便于错误定位和报告
* </p>
*/
public final class UnexpectedToken extends ParseException {
public UnexpectedToken(String message) {
super(message);
/**
* 构造一个意外的 Token异常
*
* @param actual 实际遇到的 Token 描述
* @param line 发生异常的行号
* @param column 发生异常的列号
*/
public UnexpectedToken(String actual, int line, int column) {
super("意外的 Token: " + actual, line, column);
}
}

View File

@ -1,11 +1,21 @@
package org.jcnc.snow.compiler.parser.context;
/**
* 当源码使用了当前编译器尚未支持的语言特性或语法时抛出
* 表示在语法分析过程中使用了尚未支持的语法或语言特性时抛出的异常
* <p>
* 当用户使用了当前编译器实现尚不支持的语法关键字或特性时语法分析器将抛出此异常用于清晰提示和错误报告
* </p>
*/
public final class UnsupportedFeature extends ParseException {
public UnsupportedFeature(String message) {
super(message);
/**
* 构造一个暂未支持的语法/特性异常
*
* @param feature 未被支持的语法或特性描述
* @param line 发生异常的行号
* @param column 发生异常的列号
*/
public UnsupportedFeature(String feature, int line, int column) {
super("暂未支持的语法/特性: " + feature, line, column);
}
}

View File

@ -3,9 +3,7 @@ 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.context.*;
import org.jcnc.snow.compiler.parser.factory.TopLevelParserFactory;
import java.util.ArrayList;
@ -14,18 +12,36 @@ import java.util.StringJoiner;
/**
* 语法解析引擎ParserEngine
* <p>驱动顶层解析并在捕获异常后通过同步机制恢复防止死循环</p>
* <p>
* 负责驱动顶层语法解析并统一处理收集所有语法异常防止死循环确保整体解析流程的健壮性与鲁棒性
* 支持基于同步点的错误恢复适用于命令式和脚本式语法环境
* </p>
*
* <p>
* 本引擎以异常收集为核心设计所有捕获到的 {@link ParseException} 会被聚合在分析结束后一次性统一抛出
* 同时在解析出错时会通过同步synchronize机制跳过错误片段以恢复到有效解析点避免因指针停滞导致的死循环
* </p>
*/
public record ParserEngine(ParserContext ctx) {
/** 解析整份 TokenStream返回顶层 AST 节点列表。 */
/**
* 解析整个 TokenStream返回顶层 AST 节点列表
* <p>
* 过程中如遇语法异常均会被收集并在最后聚合抛出避免单点失败导致整个解析中断
* </p>
*
* @return 解析所得的顶层 AST 节点列表
* @throws UnexpectedToken 当存在语法错误时统一抛出聚合异常
*/
public List<Node> parse() {
List<Node> nodes = new ArrayList<>();
List<String> errs = new ArrayList<>();
TokenStream ts = ctx.getTokens();
List<ParseError> errs = new ArrayList<>();
TokenStream ts = ctx.getTokens();
String file = ctx.getSourceName();
// 主循环至 EOF
while (ts.isAtEnd()) {
while (!ts.isAtEnd()) {
// 跳过空行
if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
@ -35,39 +51,46 @@ public record ParserEngine(ParserContext ctx) {
TopLevelParser parser = TopLevelParserFactory.get(ts.peek().getLexeme());
try {
nodes.add(parser.parse(ctx));
} catch (Exception ex) {
errs.add(ex.getMessage());
synchronize(ts); // 出错后同步恢复
} catch (ParseException ex) {
// 收集错误并尝试同步
errs.add(new ParseError(file, ex.getLine(), ex.getColumn(), ex.getReason()));
synchronize(ts);
}
}
// 聚合并抛出全部语法错误
/* ───── 统一抛出聚合异常 ───── */
if (!errs.isEmpty()) {
StringJoiner sj = new StringJoiner("\n - ", "", "");
errs.forEach(sj::add);
throw new UnexpectedToken("解析过程中检测到 "
+ errs.size() + " 处错误:\n - " + sj);
errs.forEach(e -> sj.add(e.toString()));
String msg = "解析过程中检测到 " + errs.size() + " 处错误:\n - " + sj;
throw new UnexpectedToken(msg, 0, 0);
}
return nodes;
}
/**
* 同步跳过当前行或直到遇到 **显式注册** 的顶层关键字
* 这样可避免因默认脚本解析器导致指针停滞而进入死循环
* 同步跳过当前行或直到遇到显式注册的顶层关键字
* <p>
* 该机制用于语法出错后恢复到下一个可能的有效解析点防止指针停滞导致死循环或重复抛错
* 同步过程中会优先跳过本行所有未识别 token并在遇到换行或注册关键字时停止随后跳过连续空行
* </p>
*
* @param ts 词法 token
*/
private void synchronize(TokenStream ts) {
while (ts.isAtEnd()) {
while (!ts.isAtEnd()) {
if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
break;
}
if (TopLevelParserFactory.isRegistered(ts.peek().getLexeme())) {
break; // 仅在已注册关键字处停下
break; // 仅在已注册关键字处停下
}
ts.next(); // 继续丢弃 token
ts.next(); // 继续丢弃 token
}
// 后续连续空行
while (ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) {
// 后续连续空行
while (!ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
}
}

View File

@ -13,64 +13,57 @@ import java.util.HashMap;
import java.util.Map;
/**
* {@code PrattExpressionParser} 基于 Pratt 算法实现的表达式解析器
* {@code PrattExpressionParser} 基于 Pratt 算法的表达式解析器实现
* <p>
* 它支持灵活的运算符优先级控制结合前缀PrefixParselet和中缀InfixParselet解析器
* 可高效解析复杂表达式结构包括
* <ul>
* <li>字面量数字字符串</li>
* <li>标识符</li>
* <li>函数调用成员访问</li>
* <li>带括号的表达式二元运算符</li>
* </ul>
* 本类提供统一注册机制和递归表达式解析入口
* 该类通过前缀PrefixParselet和中缀InfixParselet解析器注册表
* 支持灵活扩展的表达式语法包括字面量变量函数调用成员访问和各种运算符表达式
* </p>
* <p>
* 运算符优先级通过枚举控制结合递归解析实现高效的优先级处理和语法结构解析
* 未注册的语法类型或运算符会统一抛出 {@link UnsupportedFeature} 异常
* </p>
*/
public class PrattExpressionParser implements ExpressionParser {
/**
* 前缀解析器注册表 Token 类型映射
*/
/** 前缀解析器注册表(按 Token 类型名索引) */
private static final Map<String, PrefixParselet> prefixes = new HashMap<>();
/**
* 中缀解析器注册表按运算符词素映射
*/
/** 中缀解析器注册表(按运算符词素索引) */
private static final Map<String, InfixParselet> infixes = new HashMap<>();
static {
// 注册前缀解析器
prefixes.put(TokenType.NUMBER_LITERAL.name(), new NumberLiteralParselet());
prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet());
prefixes.put(TokenType.LPAREN.name(), new GroupingParselet());
prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet());
prefixes.put(TokenType.BOOL_LITERAL.name(), new BoolLiteralParselet());
// 前缀解析器注册
prefixes.put(TokenType.NUMBER_LITERAL.name(), new NumberLiteralParselet());
prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet());
prefixes.put(TokenType.LPAREN.name(), new GroupingParselet());
prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet());
prefixes.put(TokenType.BOOL_LITERAL.name(), new BoolLiteralParselet());
// 注册一元前缀运算
// 一元前缀运算
prefixes.put(TokenType.MINUS.name(), new UnaryOperatorParselet());
prefixes.put(TokenType.NOT.name(), new UnaryOperatorParselet());
prefixes.put(TokenType.NOT.name(), new UnaryOperatorParselet());
// 注册中缀解析器
infixes.put("+", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("-", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("*", new BinaryOperatorParselet(Precedence.PRODUCT, true));
infixes.put("/", new BinaryOperatorParselet(Precedence.PRODUCT, true));
infixes.put("%", new BinaryOperatorParselet(Precedence.PRODUCT, true));
infixes.put(">", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("<", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("==", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("!=", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put(">=", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("<=", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("(", new CallParselet());
infixes.put(".", new MemberParselet());
// 中缀解析器注册
infixes.put("+", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("-", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("*", new BinaryOperatorParselet(Precedence.PRODUCT, true));
infixes.put("/", new BinaryOperatorParselet(Precedence.PRODUCT, true));
infixes.put("%", new BinaryOperatorParselet(Precedence.PRODUCT, true));
infixes.put(">", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("<", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("==", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("!=", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put(">=", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("<=", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("(", new CallParselet());
infixes.put(".", new MemberParselet());
}
/**
* 表达式解析入口使用最低优先级启动递归解析
* 表达式解析统一入口
* 以最低优先级启动递归下降适配任意表达式复杂度
*
* @param ctx 当前语法解析上下文
* @return 表达式抽象语法树节点
* @param ctx 当前解析上下文
* @return 解析后的表达式 AST 节点
*/
@Override
public ExpressionNode parse(ParserContext ctx) {
@ -78,28 +71,41 @@ public class PrattExpressionParser implements ExpressionParser {
}
/**
* 根据指定优先级解析表达式
* 按指定优先级解析表达式Pratt 算法主循环
* <p>
* 先根据当前 Token 类型查找前缀解析器进行初始解析
* 然后根据优先级不断递归处理中缀运算符和右侧表达式
* </p>
*
* @param ctx 当前上下文
* @param prec 当前优先级阈值
* @param ctx 解析上下文
* @param prec 当前运算符优先级阈值
* @return 构建完成的表达式节点
* @throws UnsupportedFeature 若遇到未注册的前缀或中缀解析器
*/
ExpressionNode parseExpression(ParserContext ctx, Precedence prec) {
Token token = ctx.getTokens().next();
PrefixParselet prefix = prefixes.get(token.getType().name());
if (prefix == null) {
throw new UnsupportedFeature("没有为该 Token 类型注册前缀解析器: " + token.getType());
throw new UnsupportedFeature(
"没有为该 Token 类型注册前缀解析器: " + token.getType(),
token.getLine(),
token.getCol()
);
}
ExpressionNode left = prefix.parse(ctx, token);
while (ctx.getTokens().isAtEnd()
while (!ctx.getTokens().isAtEnd()
&& prec.ordinal() < nextPrecedence(ctx)) {
String lex = ctx.getTokens().peek().getLexeme();
InfixParselet infix = infixes.get(lex);
if (infix == null) {
Token t = ctx.getTokens().peek();
throw new UnsupportedFeature(
"没有为该 Token 类型注册中缀解析器: " + token.getType());
"没有为该运算符注册中缀解析器: '" + lex + "'",
t.getLine(),
t.getCol()
);
}
left = infix.parse(ctx, left);
}
@ -107,10 +113,10 @@ public class PrattExpressionParser implements ExpressionParser {
}
/**
* 获取下一个中缀解析器的优先级用于判断是否继续解析
* 获取下一个中缀解析器的优先级Pratt 算法核心
*
* @param ctx 当前上下文
* @return 优先级枚举 ordinal 若无解析器则为 -1
* @param ctx 当前解析上下文
* @return 下一个中缀运算符的优先级序号若无解析器则为 -1
*/
private int nextPrecedence(ParserContext ctx) {
InfixParselet infix = infixes.get(ctx.getTokens().peek().getLexeme());

View File

@ -1,12 +1,12 @@
package org.jcnc.snow.compiler.parser.module;
import org.jcnc.snow.compiler.lexer.token.TokenType;
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
import org.jcnc.snow.compiler.parser.ast.ImportNode;
import org.jcnc.snow.compiler.parser.ast.ModuleNode;
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.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;
@ -14,88 +14,85 @@ import java.util.ArrayList;
import java.util.List;
/**
* {@code ModuleParser} 类负责解析源码中的模块定义结构属于顶层结构解析器的一种
* {@code ModuleParser} 负责解析源码中的模块结构是顶层结构解析器实现之一
* <p>
* 模块中可包含多个导入语句和函数定义导入语句可在模块中任意位置出现
* 同时支持空行空行将被自动忽略不影响语法结构的正确性
* 模块定义可包含多个导入import语句和函数定义function
* 导入语句可在模块中任意位置出现且允许模块体中穿插任意数量的空行空行会被自动忽略不影响语法结构
* </p>
*
* <p>
* 典型模块语法结构
* <pre>
* module: mymod
* import ...
* function ...
* ...
* end module
* </pre>
* </p>
*/
public class ModuleParser implements TopLevelParser {
/**
* 解析一个模块定义块返回构建好的 {@link ModuleNode} 对象
* 解析一个模块定义块返回完整的 {@link ModuleNode} 语法树节点
* <p>
* 本方法的语法流程包括
* 解析过程包括
* <ol>
* <li>匹配模块声明开头 {@code module: IDENTIFIER}</li>
* <li>收集模块体中的 import 语句与 function 定义允许穿插空行</li>
* <li>模块结尾必须为 {@code end module}且后接换行符</li>
* <li>匹配模块声明起始 {@code module: IDENTIFIER}</li>
* <li>收集模块体内所有 import function 语句允许穿插空行</li>
* <li>匹配模块结束 {@code end module}</li>
* </ol>
* 所有语法错误将在解析过程中抛出异常以便准确反馈问题位置和原因
* 若遇到未识别的语句将抛出 {@link UnexpectedToken} 异常定位错误位置和原因
* </p>
*
* @param ctx 当前解析器上下文包含词法流状态信息等
* @return 返回一个 {@link ModuleNode} 实例表示完整模块的语法结构
* @throws UnexpectedToken 当模块体中出现未识别的语句时抛出
* @param ctx 当前解析上下文包含词法流等状态
* @return 解析得到的 {@link ModuleNode} 实例
* @throws UnexpectedToken 当模块体中出现未识别的顶层语句时抛出
*/
@Override
public ModuleNode parse(ParserContext ctx) {
// 获取当前上下文中提供的词法流
TokenStream ts = ctx.getTokens();
// 获取当前 token 的行号列号和文件名
int line = ctx.getTokens().peek().getLine();
int column = ctx.getTokens().peek().getCol();
int line = ts.peek().getLine();
int column = ts.peek().getCol();
String file = ctx.getSourceName();
// 期望模块声明以关键字 "module:" 开始
ts.expect("module");
ts.expect(":");
// 读取模块名称要求为标识符类型的词法单元
String name = ts.expectType(TokenType.IDENTIFIER).getLexeme();
// 模块声明必须以换行符结束
ts.expectType(TokenType.NEWLINE);
// 初始化模块的导入节点列表与函数节点列表
List<ImportNode> imports = new ArrayList<>();
List<FunctionNode> functions = new ArrayList<>();
// 创建 import function 的子解析器
ImportParser importParser = new ImportParser();
FunctionParser funcParser = new FunctionParser();
// 进入模块主体内容解析循环
while (true) {
// 跳过所有空行即连续的 NEWLINE
if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
continue;
}
// 若遇到 "end"则表明模块定义结束
if ("end".equals(ts.peek().getLexeme())) {
break;
}
// 根据当前行首关键字决定解析器的选择
String lex = ts.peek().getLexeme();
if ("import".equals(lex)) {
// 调用导入语句解析器解析多个模块导入节点
imports.addAll(importParser.parse(ctx));
} else if ("function".equals(lex)) {
// 调用函数定义解析器解析单个函数结构
functions.add(funcParser.parse(ctx));
} else {
// 遇到无法识别的语句开头抛出异常并提供详细提示
throw new UnexpectedToken("Unexpected token in module: " + lex);
throw new UnexpectedToken(
"Unexpected token in module: " + lex,
ts.peek().getLine(),
ts.peek().getCol()
);
}
}
// 确保模块体以 "end module" 结束
ts.expect("end");
ts.expect("module");
// 构建并返回完整的模块语法树节点
return new ModuleNode(name, imports, functions, line, column, file);
}
}

View File

@ -2,8 +2,8 @@ package org.jcnc.snow.compiler.parser.statement;
import org.jcnc.snow.compiler.lexer.token.TokenType;
import org.jcnc.snow.compiler.parser.ast.AssignmentNode;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.ExpressionStatementNode;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
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;
@ -11,66 +11,57 @@ import org.jcnc.snow.compiler.parser.context.UnexpectedToken;
import org.jcnc.snow.compiler.parser.expression.PrattExpressionParser;
/**
* {@code ExpressionStatementParser} 负责解析通用表达式语句包括赋值语句和单一表达式语句
* {@code ExpressionStatementParser} 用于解析通用表达式语句赋值或普通表达式
* <p>
* 支持的语法结构如下
* 支持以下两种语法结构
* <pre>{@code
* x = 1 + 2 // 赋值语句
* doSomething() // 函数调用等普通表达式语句
* doSomething() // 一般表达式语句
* }</pre>
* <ul>
* <li>以标识符开头且后接等号 {@code =}则视为赋值语句解析为 {@link AssignmentNode}</li>
* <li>以标识符开头且后接 {@code =} 解析为 {@link AssignmentNode}</li>
* <li>否则视为普通表达式解析为 {@link ExpressionStatementNode}</li>
* <li>所有表达式语句必须以换行符 {@code NEWLINE} 结束</li>
* <li>所有表达式语句必须以换行符{@code NEWLINE}结尾</li>
* </ul>
* 不允许以关键字或空行作为表达式的起始若遇到非法开头将抛出解析异常
* 若语句起始为关键字或空行将直接抛出异常防止非法语法进入表达式解析流程
*/
public class ExpressionStatementParser implements StatementParser {
/**
* 解析一个表达式语句根据上下文决定其为赋值或一般表达式
* <p>
* 具体逻辑如下
* <ol>
* <li>若当前行为标识符后接等号则作为赋值处理</li>
* <li>否则解析整个表达式作为单独语句</li>
* <li>所有语句都必须以换行符结束</li>
* <li>若表达式以关键字或空行开头将立即抛出异常避免非法解析</li>
* </ol>
* 解析单行表达式语句根据上下文判断其为赋值语句或普通表达式语句
*
* @param ctx 当前解析上下文提供词法流与状态信息
* @return 返回 {@link AssignmentNode} {@link ExpressionStatementNode} 表示的语法节点
* @throws UnexpectedToken 表达式起始为关键字或语法非法
* @param ctx 当前解析上下文提供词法流与环境信息
* @return {@link AssignmentNode} {@link ExpressionStatementNode} 语法节点
* @throws UnexpectedToken 若遇到非法起始关键字空行等
*/
@Override
public StatementNode parse(ParserContext ctx) {
TokenStream ts = ctx.getTokens();
// 快速检查若遇空行或关键字开头不可作为表达式语句
if (ts.peek().getType() == TokenType.NEWLINE || ts.peek().getType() == TokenType.KEYWORD) {
throw new UnexpectedToken("无法解析以关键字开头的表达式: " + ts.peek().getLexeme());
throw new UnexpectedToken(
"无法解析以关键字开头的表达式: " + ts.peek().getLexeme(),
ts.peek().getLine(),
ts.peek().getCol()
);
}
// 获取当前 token 的行号列号和文件名
int line = ctx.getTokens().peek().getLine();
int column = ctx.getTokens().peek().getCol();
int line = ts.peek().getLine();
int column = ts.peek().getCol();
String file = ctx.getSourceName();
// 处理赋值语句格式为 identifier = expression
if (ts.peek().getType() == TokenType.IDENTIFIER
&& ts.peek(1).getLexeme().equals("=")) {
String varName = ts.next().getLexeme(); // 消耗标识符
ts.expect("="); // 消耗等号
ExpressionNode value = new PrattExpressionParser().parse(ctx); // 解析表达式
ts.expectType(TokenType.NEWLINE); // 语句必须以换行符结束
return new AssignmentNode(varName, value, line, column, file); // 返回赋值节点
// 赋值语句IDENTIFIER = expr
if (ts.peek().getType() == TokenType.IDENTIFIER && "=".equals(ts.peek(1).getLexeme())) {
String varName = ts.next().getLexeme();
ts.expect("=");
ExpressionNode value = new PrattExpressionParser().parse(ctx);
ts.expectType(TokenType.NEWLINE);
return new AssignmentNode(varName, value, line, column, file);
}
// 处理普通表达式语句如函数调用字面量运算表达式等
// 普通表达式语句
ExpressionNode expr = new PrattExpressionParser().parse(ctx);
ts.expectType(TokenType.NEWLINE); // 语句必须以换行符结束
return new ExpressionStatementNode(expr, line, column, file); // 返回表达式语句节点
ts.expectType(TokenType.NEWLINE);
return new ExpressionStatementNode(expr, line, column, file);
}
}

View File

@ -10,84 +10,78 @@ import java.util.function.BiConsumer;
import java.util.function.Predicate;
/**
* {@code FlexibleSectionParser} 是一个通用的语法块解析工具
* {@code FlexibleSectionParser} 是一个通用的区块Section解析工具
* <p>
* 该工具支持解析由关键字标识的多段结构化区块内容常用于解析函数模块循环等语法单元中的命名子结构
* 相比传统硬编码方式提供更灵活可组合的解析能力允许解析器模块动态注册处理逻辑而非将所有逻辑写死在主流程中
* 支持通过注册表驱动的方式解析具有区块关键字标识的多段结构内容
* 常用于函数模块循环等语法单元中的命名子结构
* 通过外部注册解析逻辑支持高度可扩展与复用
* </p>
*
* <p>典型应用包括
* <p>
* 典型用途包括
* <ul>
* <li>函数体解析中的 {@code params}{@code returns}{@code body} 等部分</li>
* <li>模块定义中的 {@code imports}{@code functions} 等部分</li>
* <li>用户自定义 DSL 的可扩展语法结构</li>
* <li>可扩展 DSL 的结构化语法区块</li>
* </ul>
* </p>
*
* <p>该工具具备以下能力
* <p>主要特性</p>
* <ul>
* <li>自动跳过注释与空行</li>
* <li>根据区块名称调用外部提供的解析器</li>
* <li>支持终止标志 {@code end}来退出解析流程</li>
* <li>区块入口通过关键字匹配和可选条件判断</li>
* <li>解析逻辑由外部以函数式接口方式注册</li>
* <li>支持遇到终止关键字 {@code end}时自动停止</li>
* </ul>
*/
public class FlexibleSectionParser {
/**
* 启动结构化区块的统一解析流程
* <p>
* 每次调用会
* <ol>
* <li> token 流中跳过空行与注释</li>
* <li>依照当前 token 判断是否匹配某个区块</li>
* <li>调用对应 {@link SectionDefinition} 执行区块解析逻辑</li>
* <li>若遇到 {@code end} 关键字则终止解析过程</li>
* <li>若当前 token 不匹配任何已注册区块抛出异常</li>
* </ol>
* 解析并分派处理多区块结构
*
* @param ctx 当前解析上下文提供语法环境与作用域信息
* @param tokens 当前 token
* @param sectionDefinitions 各个区块的定义映射key 为关键字value 为判断 + 解析逻辑组合
* @throws UnexpectedToken 若出现无法识别的关键字或未满足的匹配条件
* @param ctx 解析上下文
* @param tokens 词法流
* @param sectionDefinitions 区块处理注册表key 为区块关键字value 为对应的处理定义
* @throws UnexpectedToken 遇到未注册或条件不符的关键字时抛出
*/
public static void parse(ParserContext ctx,
TokenStream tokens,
Map<String, SectionDefinition> sectionDefinitions) {
// 跳过开头的注释或空行
skipCommentsAndNewlines(tokens);
while (true) {
// 跳过当前区块之间的空白与注释
skipCommentsAndNewlines(tokens);
String keyword = tokens.peek().getLexeme();
// 结束关键字表示解析流程终止
if ("end".equals(keyword)) {
break;
}
// 查找匹配的区块定义
SectionDefinition definition = sectionDefinitions.get(keyword);
if (definition != null && definition.condition().test(tokens)) {
definition.parser().accept(ctx, tokens); // 执行解析逻辑
definition.parser().accept(ctx, tokens);
} else {
throw new UnexpectedToken("未识别的关键字或条件不满足: " + keyword);
throw new UnexpectedToken(
"未识别的关键字或条件不满足: " + keyword,
tokens.peek().getLine(),
tokens.peek().getCol()
);
}
}
}
/**
* 跳过连续出现的注释行或空行NEWLINE
* <p>
* 该方法用于在区块之间清理无效 token避免影响结构判断
* 跳过所有连续的注释COMMENT和空行NEWLINEtoken
*
* @param tokens 当前 token
* @param tokens 当前词法流
*/
private static void skipCommentsAndNewlines(TokenStream tokens) {
while (true) {
TokenType type = tokens.peek().getType();
if (type == TokenType.COMMENT || type == TokenType.NEWLINE) {
tokens.next(); // 跳过注释或换行
tokens.next();
continue;
}
break;
@ -95,17 +89,10 @@ public class FlexibleSectionParser {
}
/**
* 表示一个结构区块的定义包含匹配条件与解析器
* <p>
* 每个区块由两部分组成
* <ul>
* <li>{@code condition}用于判断当前 token 是否应进入该区块</li>
* <li>{@code parser}该区块对应的实际解析逻辑</li>
* </ul>
* 可实现懒加载多语言支持或 DSL 的结构化扩展
* 区块定义包含进入区块的判断条件与具体解析逻辑
*
* @param condition 判断是否触发该区块的谓词函数
* @param parser 区块解析逻辑消费语法上下文与 token
* @param condition 匹配区块的前置条件
* @param parser 区块内容的具体解析操作
*/
public record SectionDefinition(Predicate<TokenStream> condition,
BiConsumer<ParserContext, TokenStream> parser) {

View File

@ -6,77 +6,61 @@ import java.util.*;
import java.util.Map.Entry;
/**
* JSON 工具类提供线程安全可重用的解析与序列化
* JSON 工具类提供线程安全可重用的 JSON 解析与序列化能
* <p>
* - 解析将合法的 JSON 文本转换为 Java 原生对象MapListStringNumberBoolean null
* - 序列化 Java 原生对象转换为符合 JSON 标准的字符串
* <p>
* 设计要点
* 1. 使用静态方法作为唯一入口避免状态共享导致的线程安全问题
* 2. 解析器内部使用 char[] 缓冲区提高访问性能
* 3. 维护行列号信息抛出异常时能精确定位错误位置
* 4. 序列化器基于 StringBuilder预分配容量减少中间字符串创建
* <b>主要功能</b>
* <ul>
* <li>解析将合法的 JSON 文本转换为 Java 原生对象MapListStringNumberBoolean null</li>
* <li>序列化 Java 原生对象转换为符合 JSON 标准的字符串</li>
* </ul>
* </p>
*
* <b>设计要点</b>
* <ol>
* <li>仅提供静态方法入口无状态线程安全</li>
* <li>解析器内部采用 char[] 缓冲区支持高性能处理</li>
* <li>精确维护行列号信息异常可定位错误文本位置</li>
* <li>序列化器使用 StringBuilder默认预分配容量</li>
* </ol>
*/
public class JSONParser {
private JSONParser() {
}
private JSONParser() {}
/**
* JSON 文本解析为对应的 Java 对象
* 解析 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 UnexpectedToken 如果遇到语法错误或多余字符异常消息中包含行列信息
* @return 解析得到的 Java 对象MapListStringNumberBoolean null
* @throws UnexpectedToken 语法错误或多余字符异常消息带行列定位
*/
public static Object parse(String input) {
return new Parser(input).parseInternal();
}
/**
* Java 原生对象序列化为 JSON 字符串
* Java 原生对象序列化为 JSON 字符串
*
* @param obj 支持的类型MapCollectionStringNumberBoolean null
* @param obj 支持 MapCollectionStringNumberBoolean null
* @return 符合 JSON 规范的字符串
* @throws UnsupportedOperationException 遇到不支持的类型时抛出
*/
public static String toJson(Object obj) {
return Writer.write(obj);
}
// ======= 内部解析器 =======
// ======= 内部解析器实现 =======
/**
* 负责将 char[] 缓冲区中的 JSON 文本解析为 Java 对象
* 负责将 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;
@ -85,7 +69,7 @@ public class JSONParser {
}
/**
* 入口方法跳过空白后调用 parseValue校验尾部无多余字符
* 解析主入口校验无多余字符
*/
Object parseInternal() {
skipWhitespace();
@ -98,7 +82,7 @@ public class JSONParser {
}
/**
* 根据下一个字符决定解析哪种 JSON
* 解析 JSON null, true, false, string, number, object, array
*/
private Object parseValue() {
skipWhitespace();
@ -111,33 +95,31 @@ public class JSONParser {
if (c == '[') return parseArray();
if (c == '-' || isDigit(c)) return parseNumber();
error("遇到意外字符 '" + c + "'");
return null; // 永不到达
return null;
}
/**
* 解析 JSON 对象返回 Map<String, Object>
* 解析对象类型 { ... }
*/
private Map<String, Object> parseObject() {
expect('{'); // 跳过 '{'
expect('{');
skipWhitespace();
Map<String, Object> map = new LinkedHashMap<>();
// 空对象 {}
if (currentChar() == '}') {
advance(); // 跳过 '}'
advance();
return map;
}
// 多成员对象解析
while (true) {
skipWhitespace();
String key = parseString(); // 解析键
String key = parseString();
skipWhitespace();
expect(':');
skipWhitespace();
Object val = parseValue(); // 解析值
Object val = parseValue();
map.put(key, val);
skipWhitespace();
if (currentChar() == '}') {
advance(); // 跳过 '}'
advance();
break;
}
expect(',');
@ -147,18 +129,16 @@ public class JSONParser {
}
/**
* 解析 JSON 数组返回 List<Object>
* 解析数组类型 [ ... ]
*/
private List<Object> parseArray() {
expect('[');
skipWhitespace();
List<Object> list = new ArrayList<>();
// 空数组 []
if (currentChar() == ']') {
advance(); // 跳过 ']'
advance();
return list;
}
// 多元素数组解析
while (true) {
skipWhitespace();
list.add(parseValue());
@ -174,46 +154,38 @@ public class JSONParser {
}
/**
* 解析 JSON 字符串文字处理转义字符
* 解析字符串类型支持标准 JSON 转义
*/
private String parseString() {
expect('"'); // 跳过开头 '"'
expect('"');
StringBuilder sb = new StringBuilder();
while (true) {
char c = currentChar();
if (c == '"') {
advance(); // 跳过结束 '"'
advance();
break;
}
if (c == '\\') {
advance(); // 跳过 '\'
advance();
c = currentChar();
switch (c) {
case '"':
sb.append('"');
break;
sb.append('"'); break;
case '\\':
sb.append('\\');
break;
sb.append('\\'); break;
case '/':
sb.append('/');
break;
sb.append('/'); break;
case 'b':
sb.append('\b');
break;
sb.append('\b'); break;
case 'f':
sb.append('\f');
break;
sb.append('\f'); break;
case 'n':
sb.append('\n');
break;
sb.append('\n'); break;
case 'r':
sb.append('\r');
break;
sb.append('\r'); break;
case 't':
sb.append('\t');
break;
case 'u': // 解析 Unicode 转义
sb.append('\t'); break;
case 'u':
String hex = new String(buf, pos + 1, 4);
sb.append((char) Integer.parseInt(hex, 16));
pos += 4;
@ -232,15 +204,14 @@ public class JSONParser {
}
/**
* 解析 JSON 数值支持整数浮点及科学计数法
* 解析数字类型支持整数小数科学计数法
*/
private Number parseNumber() {
int start = pos;
if (currentChar() == '-') advance();
while (isDigit(currentChar())) advance();
if (currentChar() == '.') {
do advance();
while (isDigit(currentChar()));
do { advance(); } while (isDigit(currentChar()));
}
if (currentChar() == 'e' || currentChar() == 'E') {
advance();
@ -248,7 +219,6 @@ public class JSONParser {
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);
}
@ -260,7 +230,7 @@ public class JSONParser {
}
/**
* 跳过所有空白字符支持空格制表符回车换行
* 跳过所有空白含换行同时维护行/列号
*/
private void skipWhitespace() {
while (pos < buf.length) {
@ -273,21 +243,17 @@ public class JSONParser {
}
}
/**
* 获取当前位置字符超出范围返回 '\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;
line++; col = 1;
} else {
col++;
}
@ -296,7 +262,7 @@ public class JSONParser {
}
/**
* 验证当前位置字符等于预期字符否则抛出错误
* 匹配下一个字符或字符串并前移指针
*/
private void expect(char c) {
if (currentChar() != c) {
@ -306,7 +272,7 @@ public class JSONParser {
}
/**
* 尝试匹配给定字符串匹配成功则移动位置并返回 true
* 判断当前位置是否能完整匹配目标字符串若能则移动指针
*/
private boolean match(String s) {
int len = s.length();
@ -318,35 +284,30 @@ public class JSONParser {
return true;
}
/**
* 判断字符是否为数字
*/
private boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
/**
* 抛出带行列定位的解析错误
* 抛出带行列号的解析异常
*/
private void error(String msg) {
throw new UnexpectedToken("在第 " + line + " 行,第 " + col + " 列出现错误: " + msg);
throw new UnexpectedToken(
"在第 " + line + " 行,第 " + col + " 列出现错误: " + msg,
line,
col
);
}
}
// ======= 内部序列化器 =======
// ======= 内部序列化器实现 =======
/**
* 负责高效地将 Java 对象写为 JSON 文本
* 负责 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);
@ -354,7 +315,7 @@ public class JSONParser {
}
/**
* 根据对象类型选择合适的写入方式
* 递归输出任意支持的 JSON 类型对象
*/
private static void writeValue(Object obj, StringBuilder sb) {
if (obj == null) {
@ -390,27 +351,22 @@ public class JSONParser {
}
/**
* 为字符串添加双引号并转义必须的字符
* JSON 字符串输出处理所有必要的转义字符
*/
private static void quote(String s, StringBuilder sb) {
sb.append('"');
for (char c : s.toCharArray()) {
switch (c) {
case '\\':
sb.append("\\\\");
break;
sb.append("\\\\"); break;
case '"':
sb.append("\\\"");
break;
sb.append("\\\""); break;
case '\n':
sb.append("\\n");
break;
sb.append("\\n"); break;
case '\r':
sb.append("\\r");
break;
sb.append("\\r"); break;
case '\t':
sb.append("\\t");
break;
sb.append("\\t"); break;
default:
sb.append(c);
}