refactor: 重构表达式解析器文档和代码

- 更新类文档,使其更清晰地描述 Pratt 表达式解析器的功能和结构
- 优化方法注释,增加对核心解析逻辑的解释
- 调整代码格式,提高可读性
- 补充注释说明前缀和中缀解析器的注册方式及使用场景
This commit is contained in:
Luke 2025-08-29 18:19:24 +08:00
parent 17b078b6f3
commit 586ede1cf0

View File

@ -13,62 +13,62 @@ import java.util.HashMap;
import java.util.Map;
/**
* {@code PrattExpressionParser} 基于 Pratt 算法的表达式解析器实现
* {@code PrattExpressionParser}
* <p>
* 该类通过前缀PrefixParselet和中缀InfixParselet解析器注册表
* 支持灵活扩展的表达式语法包括字面量变量函数调用成员访问和各种运算符表达式
* </p>
* <p>
* 运算符优先级通过枚举控制结合递归解析实现高效的优先级处理和语法结构解析
* 未注册的语法类型或运算符会统一抛出 {@link UnsupportedFeature} 异常
* 基于 Pratt 算法的表达式解析器经典运算符优先级递归解析框架
* <ul>
* <li>支持注册前缀和中缀解析器灵活组合表达式语法</li>
* <li>支持字面量变量函数调用成员访问对象创建各类一元/二元/多元运算</li>
* <li>可快速扩展新语法只需注册新的 Parselet 即可</li>
* <li>出错时统一抛出 {@link UnsupportedFeature}便于调试和错误提示</li>
* </ul>
* </p>
*/
public class PrattExpressionParser implements ExpressionParser {
/**
* 前缀解析器注册表 Token 类型名或词素索引
* <p>
* 用于存储所有支持的前缀表达式解析器例如字面量变量分组数组一元运算等
* 支持通过 TokenType 的名称和特定词素 "(", "["两种方式索引
* </p>
* 前缀解析器注册表通过 Token 类型名或词素作为索引
* <ul>
* <li>如数字字面量标识符字符串布尔值new分组数组一元运算等</li>
* <li>支持同时用 TokenType 名称和具体词素 "(""-"注册</li>
* </ul>
*/
private static final Map<String, PrefixParselet> prefixes = new HashMap<>();
/**
* 中缀解析器注册表运算符词素索引
* <p>
* 用于存储所有支持的中缀表达式解析器如二元运算函数调用下标成员访问等
* 通过词素索引 "+", "-", "(", "["
* </p>
* 中缀解析器注册表通过运算符词素索引
* <ul>
* <li> + - * / % 及比较逻辑函数调用下标成员访问等</li>
* <li>词素索引 "+""-""(""."</li>
* </ul>
*/
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.STRING_LITERAL.name(), new StringLiteralParselet());
// 注册布尔字面量解析
prefixes.put(TokenType.BOOL_LITERAL.name(), new BoolLiteralParselet());
// 支持括号分组数组字面量
// 分组与数组字面量两种索引方式
prefixes.put(TokenType.LPAREN.name(), new GroupingParselet());
prefixes.put(TokenType.LBRACKET.name(), new ArrayLiteralParselet());
// 兼容直接以词素注册 '(' '['
prefixes.put("(", new GroupingParselet());
prefixes.put("[", new ArrayLiteralParselet());
// 一元前缀运算符负号逻辑非
// 一元前缀运算符负号逻辑非同样用两种方式注册
prefixes.put(TokenType.MINUS.name(), new UnaryOperatorParselet());
prefixes.put(TokenType.NOT.name(), new UnaryOperatorParselet());
prefixes.put("-", new UnaryOperatorParselet());
prefixes.put("!", new UnaryOperatorParselet());
// -------- 中缀解析器注册 --------
// 注册常见二元运算符加减乘除取模
// 对象创建 new TypeName(args...)
prefixes.put("new", new NewObjectParselet());
// ----------------- 中缀解析器注册 -----------------
// 常见二元算数运算符
infixes.put("+", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("-", new BinaryOperatorParselet(Precedence.SUM, true));
infixes.put("*", new BinaryOperatorParselet(Precedence.PRODUCT, true));
@ -79,25 +79,25 @@ public class PrattExpressionParser implements ExpressionParser {
infixes.put("<", new BinaryOperatorParselet(Precedence.COMPARISON, true));
infixes.put(">=", new BinaryOperatorParselet(Precedence.COMPARISON, true));
infixes.put("<=", new BinaryOperatorParselet(Precedence.COMPARISON, true));
// 相等性
// 相等性判断
infixes.put("==", new BinaryOperatorParselet(Precedence.EQUALITY, true));
infixes.put("!=", new BinaryOperatorParselet(Precedence.EQUALITY, true));
// 逻辑与或
// 逻辑运算
infixes.put("&&", new BinaryOperatorParselet(Precedence.AND, true));
infixes.put("||", new BinaryOperatorParselet(Precedence.OR, true));
// 调用索引成员访问
// 函数调用数组下标成员访问
infixes.put("(", new CallParselet());
infixes.put("[", new IndexParselet());
infixes.put(".", new MemberParselet());
}
/**
* 解析任意表达式的统一入口
* 统一表达式解析入口自动以最低优先级递归解析整个表达式
* <p>
* 该方法将以最低优先级启动表达式递归解析能够自动适配和处理多层嵌套或复杂组合表达式
* 能解析嵌套复合等所有合法表达式结构
* </p>
*
* @param ctx 当前解析上下文对象持有 token 流等信息
* @param ctx 当前解析上下文对象 token 流等信息
* @return 解析得到的表达式 AST 节点对象
*/
@Override
@ -106,50 +106,42 @@ public class PrattExpressionParser implements ExpressionParser {
}
/**
* 按指定优先级解析表达式Pratt 算法核心
* Pratt 算法主递归循环按给定优先级递归解析表达式
* <p>
* 1. 先取当前 token查找对应的前缀解析器进行初始解析构建表达式左侧如字面量变量等
* 2. 然后循环检测是否有更高优先级的中缀操作符
* 若有则递归处理右侧表达式并组合为新的表达式节点
* </p>
* <p>
* 未找到对应前缀或中缀解析器时会抛出 {@link UnsupportedFeature} 异常
* 实现按优先级吸收中缀操作符如连续算术链式调用组合表达式等
* </p>
*
* @param ctx 解析上下文
* @param prec 当前运算符优先级用于控制递归层级
* @return 解析构建好的表达式节点
* @throws UnsupportedFeature 遇到未注册的解析器时抛出
* @param prec 当前已绑定优先级
* @return 已解析的表达式节点
*/
ExpressionNode parseExpression(ParserContext ctx, Precedence prec) {
// 取下一个 token 作为本轮前缀表达式起始
// 1) 消耗一个 token 作为前缀起点
Token token = ctx.getTokens().next();
// 查找前缀解析器先按类型名再按词素
PrefixParselet prefix = prefixes.get(token.getType().name());
// 2) 查找前缀解析器优先按词素再按 TokenType
PrefixParselet prefix = prefixes.get(token.getLexeme());
if (prefix == null) {
prefix = prefixes.get(token.getLexeme());
prefix = prefixes.get(token.getType().name());
}
if (prefix == null) {
// 找到前缀解析器则报错
// 注册前缀解析器直接报错
throw new UnsupportedFeature(
"没有为该 Token 类型注册前缀解析器: " + token.getType(),
"没有为该 Token 注册前缀解析器: " + token.getLexeme() + " / " + token.getType(),
token.getLine(),
token.getCol()
);
}
// 执行前缀解析获得左侧表达式
// 3) 前缀解析得到左侧表达式
ExpressionNode left = prefix.parse(ctx, token);
// 不断尝试查找优先级更高的中缀运算符递归处理表达式链
while (!ctx.getTokens().isAtEnd()
&& prec.ordinal() < nextPrecedence(ctx)) {
// 查看下一个 token 词素查找中缀解析器
// 4) 主循环不断吸收更高优先级的中缀操作直到优先级不再提升
while (!ctx.getTokens().isAtEnd() && prec.ordinal() < nextPrecedence(ctx)) {
String lex = ctx.getTokens().peek().getLexeme();
InfixParselet infix = infixes.get(lex);
if (infix == null) {
// 若未注册中缀解析器则直接抛异常常见于语法错误
// nextPrecedence > prec 时一般已注册中缀解析器
Token t = ctx.getTokens().peek();
throw new UnsupportedFeature(
"没有为该运算符注册中缀解析器: '" + lex + "'",
@ -157,21 +149,21 @@ public class PrattExpressionParser implements ExpressionParser {
t.getCol()
);
}
// 使用中缀解析器处理表达式组合
// 递归组合更高优先级的中缀表达式
left = infix.parse(ctx, left);
}
// 返回本层递归已解析的表达式节点
// 5) 返回本层解析完成的表达式节点
return left;
}
/**
* 获取下一个 token 词素对应的中缀运算符优先级Pratt 算法关键
* 获取下一个 token 对应的中缀运算符优先级Pratt 算法关键
* <p>
* 用于决定当前是否需要递归处理更高优先级的中缀操作
* 若无注册的中缀解析器则返回 -1
* </p>
*
* @param ctx 当前解析上下文
* @return 下一个中缀运算符的优先级序号若无注册解析器则返回 -1
* @param ctx 解析上下文
* @return 下一个运算符优先级序号无则-1
*/
private int nextPrecedence(ParserContext ctx) {
InfixParselet infix = infixes.get(ctx.getTokens().peek().getLexeme());