增加注释

This commit is contained in:
Luke 2025-05-11 23:50:23 +08:00
parent 88c749067b
commit 3a992ccd37
20 changed files with 359 additions and 210 deletions

View File

@ -4,20 +4,28 @@ import org.jcnc.snow.compiler.parser.ast.base.Node;
import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.context.ParserContext;
/** /**
* 顶层结构解析器接口用于解析模块级别的语法结构 {@code module}{@code import}{@code function} * {@code TopLevelParser} 是顶层语法结构的解析器接口
* 所有顶层解析器应实现该接口并从 {@link ParserContext} 中读取 TokenStream 来构造 AST 节点
* <p> * <p>
* 该接口由 {@link org.jcnc.snow.compiler.parser.factory.TopLevelParserFactory} 根据当前关键字动态调度 * 用于解析模块级别的构造例如 {@code module}{@code import}{@code function}
* 所有顶层语法解析器应实现该接口并从 {@link ParserContext} 提供的 TokenStream 中提取并构建 AST 节点
* </p>
* <p>
* 本接口由 {@link org.jcnc.snow.compiler.parser.factory.TopLevelParserFactory} 负责根据关键字进行动态分派
* 以便支持扩展性与模块化解析策略
* </p>
*/ */
public interface TopLevelParser { public interface TopLevelParser {
/** /**
* 从解析上下文中解析一个顶层语法结构 * 从解析上下文中解析一个顶层语法结构节点
* 每个实现应从 TokenStream 中消费对应的 token并返回构建后的 AST 节点 * <p>
* 每个实现类应根据自身语法规则消费 TokenStream 中的相应 token
* 构造对应的 AST 子树结构
* </p>
* *
* @param ctx 当前解析上下文包含 Token 流与状态信息 * @param ctx 当前解析上下文包含 Token 流与中间状态
* @return 表示顶层结构的 AST 节点不应为 null * @return 建完成 AST 节点不应为 {@code null}
* @throws IllegalStateException 如果遇到非法语法结构 * @throws IllegalStateException 若解析过程遇到非法结构或上下文状态异常
*/ */
Node parse(ParserContext ctx); Node parse(ParserContext ctx);
} }

View File

@ -1,15 +1,22 @@
package org.jcnc.snow.compiler.parser.context; package org.jcnc.snow.compiler.parser.context;
/** /**
* 表示解析过程中发生的异常通常用于报告语法错误 * {@code ParseException} 表示语法分析阶段发生的错误
* 在语法分析器发现非法语法或无法处理的结构时抛出 * <p>
* 当语法分析器遇到非法的语法结构或无法继续处理的标记序列时
* 应抛出该异常以中断当前解析流程并向调用方报告错误信息
* </p>
* <p>
* 该异常通常由 {@code ParserContext} 或各类语法规则处理器主动抛出
* 用于提示编译器前端或 IDE 系统进行错误提示与恢复
* </p>
*/ */
public class ParseException extends RuntimeException { public class ParseException extends RuntimeException {
/** /**
* 构造一个解析异常实例并提供错误消息 * 构造一个带有错误描述信息的解析异常实例
* *
* @param message 错误描述信息用于指出语法错误的详情 * @param message 错误描述文本用于指明具体的语法错误原因
*/ */
public ParseException(String message) { public ParseException(String message) {
super(message); super(message);

View File

@ -4,25 +4,31 @@ import org.jcnc.snow.compiler.lexer.token.Token;
import java.util.List; import java.util.List;
/** /**
* 表示解析器的共享上下文封装了当前的 {@link TokenStream} * {@code ParserContext} 表示语法分析阶段的共享上下文容器
* 后续还可以扩展为包含错误收集器符号表作用域信息等 * <p>
* 封装了词法单元流TokenStream供各级语法解析器读取与回退
* 后续还可扩展为包含错误收集符号表管理作用域追踪等功能模块
* 以支持完整的编译前端功能
* </p>
*/ */
public class ParserContext { public class ParserContext {
/** 当前语法分析所使用的 Token 流 */
private final TokenStream tokens; private final TokenStream tokens;
/** /**
* 构造解析上下文并包装传入的词法单元列表为 {@link TokenStream} * 使用词法分析得到的 Token 列表构造上下文
* *
* @param tokens 词法分析器生成的 token 列表 * @param tokens 词法分析器生成的 Token 集合
*/ */
public ParserContext(List<Token> tokens) { public ParserContext(List<Token> tokens) {
this.tokens = new TokenStream(tokens); this.tokens = new TokenStream(tokens);
} }
/** /**
* 获取当前的 Token 用于驱动语法解析过程 * 获取封装的 Token 用于驱动语法分析过程
* *
* @return token 流实例 * @return 当前使用的 {@link TokenStream} 实例
*/ */
public TokenStream getTokens() { public TokenStream getTokens() {
return tokens; return tokens;

View File

@ -6,28 +6,35 @@ import org.jcnc.snow.compiler.lexer.token.TokenType;
import java.util.List; import java.util.List;
/** /**
* 封装 Token 列表并维护当前位置用于解析器消费 Token 的工具类 * {@code TokenStream} 封装了一个 Token 列表并维护当前解析位置
* 提供常见的操作如 {@code peek}查看{@code next}消费{@code match}匹配 * 是语法分析器读取词法单元的核心工具类
* {@code expect}断言支持向前查看多个 Token 以实现前瞻解析 * <p>
* 提供前瞻peek消费next匹配match断言expect等常用操作
* 支持前向查看和异常处理适用于递归下降解析等常见语法构建策略
* </p>
*/ */
public class TokenStream { public class TokenStream {
/** 源 Token 列表 */
private final List<Token> tokens; private final List<Token> tokens;
/** 当前解析位置索引 */
private int pos = 0; private int pos = 0;
/** /**
* 构造 Token 流对象 * 使用 Token 列表构造 TokenStream
* *
* @param tokens 从词法分析器生成的 Token 列表 * @param tokens 由词法分析器产生的 Token 集合
*/ */
public TokenStream(List<Token> tokens) { public TokenStream(List<Token> tokens) {
this.tokens = tokens; this.tokens = tokens;
} }
/** /**
* 向前查看指定偏移量 Token不消费 * 向前查看指定偏移量处的 Token不移动位置
* *
* @param offset 相对当前位置的偏移量例如 0 表示当前位置 * @param offset 相对当前位置的偏移量0 表示当前
* @return 对应位置的 Token如果越界则返回 EOF Token * @return 指定位置的 Token若越界则返回自动构造的 EOF Token
*/ */
public Token peek(int offset) { public Token peek(int offset) {
int idx = pos + offset; int idx = pos + offset;
@ -38,18 +45,18 @@ public class TokenStream {
} }
/** /**
* 查看当前位置的 Token {@code peek(0)} * 查看当前位置的 Token {@code peek(0)}
* *
* @return 当前 Token * @return 当前 Token
*/ */
public Token peek() { public Token peek() {
return peek(0); return peek(0);
} }
/** /**
* 消费并返回当前位置的 Token内部位置前移 * 消费当前位置的 Token 并返回位置前移
* *
* @return 当前 Token * @return 当前 Token
*/ */
public Token next() { public Token next() {
Token t = peek(); Token t = peek();
@ -58,10 +65,10 @@ public class TokenStream {
} }
/** /**
* 如果当前 Token 的词素等于指定字符串则消费该 Token 并返回 true * 匹配当前 Token 的词素与指定字符串若匹配则消费
* *
* @param lexeme 要匹配的词素 * @param lexeme 待匹配词素
* @return 是否成功匹配 * @return 若成功匹配则返回 true
*/ */
public boolean match(String lexeme) { public boolean match(String lexeme) {
if (peek().getLexeme().equals(lexeme)) { if (peek().getLexeme().equals(lexeme)) {
@ -72,11 +79,11 @@ public class TokenStream {
} }
/** /**
* 断言当前 Token 的词素等于指定字符串否则抛出 {@link ParseException} * 断言当前 Token 的词素与指定值相符否则抛出 {@link ParseException}
* *
* @param lexeme 期望的词素 * @param lexeme 期望的词素
* @return 匹配 Token * @return 匹配成功 Token
* @throws ParseException 如果词素不匹配 * @throws ParseException 若词素不符
*/ */
public Token expect(String lexeme) { public Token expect(String lexeme) {
Token t = peek(); Token t = peek();
@ -90,11 +97,11 @@ public class TokenStream {
} }
/** /**
* 断言当前 Token 的类型等于指定类型否则抛出 {@link ParseException} * 断言当前 Token 类型为指定类型否则抛出 {@link ParseException}
* *
* @param type 期望的 Token 类型 * @param type 期望的 Token 类型
* @return 匹配 Token * @return 匹配成功 Token
* @throws ParseException 如果类型不匹配 * @throws ParseException 若类型不匹配
*/ */
public Token expectType(TokenType type) { public Token expectType(TokenType type) {
Token t = peek(); Token t = peek();
@ -108,9 +115,9 @@ public class TokenStream {
} }
/** /**
* 判断是否还未到达文件结尾EOF * 判断是否尚未到达 EOF
* *
* @return 如果当前位置的 Token 不是 EOF则返回 true * @return 若当前位置 Token EOF则返回 true
*/ */
public boolean isAtEnd() { public boolean isAtEnd() {
return peek().getType() != TokenType.EOF; return peek().getType() != TokenType.EOF;

View File

@ -11,56 +11,61 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* 解析器主引擎负责驱动顶层结构 module解析 * {@code ParserEngine} 是语法分析阶段的主控引擎
* 它会循环处理每个顶层语法块直到遇到 EOF * 负责驱动顶层语法结构如模块定义导入语句函数定义等的解析流程
* 同时自动跳过空行NEWLINE以增强容错性 * <p>
* 它通过不断读取 TokenStream 中的标记动态选择合适的解析器由工厂提供
* 并构建对应的抽象语法树AST节点
* 同时具备跳过空行处理非法标记等基本容错能力
* </p>
*/ */
public class ParserEngine { public class ParserEngine {
// 解析上下文对象包含 TokenStream 及相关辅助功能
/** 解析上下文,封装 TokenStream 与语法状态 */
private final ParserContext ctx; private final ParserContext ctx;
/** /**
* 构造解析器引擎 * 构造一个 {@code ParserEngine} 实例
* *
* @param ctx 解析上下文封装了 Token 流等解析状态 * @param ctx 提供语法分析所需的上下文信息 Token
*/ */
public ParserEngine(ParserContext ctx) { public ParserEngine(ParserContext ctx) {
this.ctx = ctx; this.ctx = ctx;
} }
/** /**
* 启动解析流程返回顶层节点列表模块导入等 * 启动语法解析流程提取所有顶层 AST 节点
* <p>
* 该方法会循环调用 {@link TopLevelParserFactory} 选择合适解析器
* 并将解析结果存入返回列表直到遇到文件结束符EOF
* </p>
* *
* @return 所有解析出的 AST 顶层节点 * @return 顶层 AST 节点列表如模块导入函数等
* @throws IllegalStateException 若遇到无法识别的顶层标记
*/ */
public List<Node> parse() { public List<Node> parse() {
List<Node> nodes = new ArrayList<>(); List<Node> nodes = new ArrayList<>();
TokenStream ts = ctx.getTokens(); // 获取 Token TokenStream ts = ctx.getTokens();
// 循环解析直到文件结束
while (ts.isAtEnd()) { while (ts.isAtEnd()) {
// 跳过空行增加语法容错能力 // 跳过 NEWLINE 以增强容错性
if (ts.peek().getType() == TokenType.NEWLINE) { if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next(); // 消耗空行 ts.next();
continue; continue;
} }
// 获取当前标记 module, import, function // 当前词素作为解析关键字 module, import
String lex = ts.peek().getLexeme(); String lex = ts.peek().getLexeme();
// 通过工厂方法获得对应的顶层解析器
TopLevelParser parser = TopLevelParserFactory.get(lex); TopLevelParser parser = TopLevelParserFactory.get(lex);
// 如果找不到对应解析器说明语法结构非法或暂不支持
if (parser == null) { if (parser == null) {
throw new IllegalStateException("意外的顶级标记: " + lex); throw new IllegalStateException("意外的顶级标记: " + lex);
} }
// 使用解析器解析该顶层结构添加到结果列表中 // 执行对应解析器
nodes.add(parser.parse(ctx)); nodes.add(parser.parse(ctx));
} }
// 返回构建的 AST 节点集合
return nodes; return nodes;
} }
} }

View File

@ -7,18 +7,26 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.base.InfixParselet; import org.jcnc.snow.compiler.parser.expression.base.InfixParselet;
/** /**
* 表示中缀二元运算符的解析规则parselet用于解析如 {@code a + b} 的表达式结构 * {@code BinaryOperatorParselet} 表示用于解析二元中缀表达式的解析器
* 支持设置运算符的优先级和结合性左结合或右结合 * <p>
* 该解析器支持二元运算符表达式 {@code a + b}{@code x * y}
* 可配置操作符优先级与结合性左结合或右结合
* 适用于 Pratt 解析器架构中中缀阶段的语法处理
* </p>
*/ */
public class BinaryOperatorParselet implements InfixParselet { public class BinaryOperatorParselet implements InfixParselet {
/** 当前运算符的优先级 */
private final Precedence precedence; private final Precedence precedence;
/** 是否为左结合运算符 */
private final boolean leftAssoc; private final boolean leftAssoc;
/** /**
* 构造二元运算符的解析器 * 构造一个中缀二元运算符的解析器
* *
* @param precedence 运算符的优先级 * @param precedence 运算符的优先级
* @param leftAssoc 是否左结合true 表示左结合false 表示右结合 * @param leftAssoc 是否左结合true 表示左结合false 表示右结合
*/ */
public BinaryOperatorParselet(Precedence precedence, boolean leftAssoc) { public BinaryOperatorParselet(Precedence precedence, boolean leftAssoc) {
this.precedence = precedence; this.precedence = precedence;
@ -26,28 +34,30 @@ public class BinaryOperatorParselet implements InfixParselet {
} }
/** /**
* 解析一个中缀表达式 * 解析当前二元中缀表达式
* *
* @param ctx 解析上下文包含 Token 流等信息 * @param ctx 当前解析上下文
* @param left 当前已解析的左表达式 * @param left 当前已解析的左表达式
* @return 一个新的 {@link BinaryExpressionNode} 表达式节点 * @return 构建完成的 {@link BinaryExpressionNode} AST 节点
*/ */
@Override @Override
public ExpressionNode parse(ParserContext ctx, ExpressionNode left) { public ExpressionNode parse(ParserContext ctx, ExpressionNode left) {
Token op = ctx.getTokens().next(); // 获取当前运算符 Token Token op = ctx.getTokens().next();
int prec = precedence.ordinal(); int prec = precedence.ordinal();
// 如果是左结合运算符右表达式的优先级应减一右边绑定更紧
// 右侧表达式根据结合性确定优先级绑定
ExpressionNode right = new PrattExpressionParser().parseExpression( ExpressionNode right = new PrattExpressionParser().parseExpression(
ctx, ctx,
leftAssoc ? Precedence.values()[prec] : Precedence.values()[prec - 1] leftAssoc ? Precedence.values()[prec] : Precedence.values()[prec - 1]
); );
return new BinaryExpressionNode(left, op.getLexeme(), right); return new BinaryExpressionNode(left, op.getLexeme(), right);
} }
/** /**
* 返回该运算符的优先级 * 获取当前运算符的优先级
* *
* @return 当前运算符优先级枚举 * @return 运算符优先级枚举
*/ */
@Override @Override
public Precedence getPrecedence() { public Precedence getPrecedence() {

View File

@ -9,17 +9,21 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* 表示函数调用的解析规则parselet用于解析形如 {@code foo(arg1, arg2)} 的调用表达式 * {@code CallParselet} 表示函数调用语法的中缀解析器
* 函数调用的结构为已解析的 callee 表达式后接一对圆括号和参数列表 * <p>
* 用于处理形如 {@code foo(arg1, arg2)} 的函数调用结构
* Pratt 解析器架构中该解析器在函数名之后接收括号开始的调用参数
* 构建 {@link CallExpressionNode} 抽象语法树节点
* </p>
*/ */
public class CallParselet implements InfixParselet { public class CallParselet implements InfixParselet {
/** /**
* 解析函数调用表达式形如 {@code callee(args...)} * 解析函数调用表达式格式为 {@code callee(args...)}
* *
* @param ctx 解析上下文包含 Token 流等信息 * @param ctx 当前解析上下文
* @param left 已解析的函数名或调用目标 callee 表达式 * @param left 函数名或调用目标 callee 表达式
* @return 生成的 {@link CallExpressionNode} 表达式节点 * @return 构建完成的函数调用 AST 节点
*/ */
@Override @Override
public ExpressionNode parse(ParserContext ctx, ExpressionNode left) { public ExpressionNode parse(ParserContext ctx, ExpressionNode left) {
@ -27,21 +31,20 @@ public class CallParselet implements InfixParselet {
List<ExpressionNode> args = new ArrayList<>(); List<ExpressionNode> args = new ArrayList<>();
// 如果不是空参数列表
if (!ctx.getTokens().peek().getLexeme().equals(")")) { if (!ctx.getTokens().peek().getLexeme().equals(")")) {
do { do {
args.add(new PrattExpressionParser().parse(ctx)); args.add(new PrattExpressionParser().parse(ctx));
} while (ctx.getTokens().match(",")); // 支持多个参数用逗号分隔 } while (ctx.getTokens().match(","));
} }
ctx.getTokens().expect(")"); // 消费并 ")" ctx.getTokens().expect(")"); // 消费并 ")"
return new CallExpressionNode(left, args); return new CallExpressionNode(left, args);
} }
/** /**
* 获取函数调用的优先级通常是最高级别之一 * 获取函数调用操作的优先级
* *
* @return {@link Precedence#CALL} 表示函数调用的绑定紧密度 * @return 表达式优先级 {@link Precedence#CALL}
*/ */
@Override @Override
public Precedence getPrecedence() { public Precedence getPrecedence() {

View File

@ -6,17 +6,24 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet; import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet;
/** /**
* 解析圆括号包裹的表达式例如 {@code (a + b)} * {@code GroupingParselet} 解析圆括号括起的子表达式
* 用于提升子表达式的优先级使其在整体表达式中优先计算 * <p>
* 用于处理形如 {@code (a + b)} 的表达式结构
* 通常用于提升括号内表达式的优先级以控制运算顺序
* </p>
*/ */
public class GroupingParselet implements PrefixParselet { public class GroupingParselet implements PrefixParselet {
/** /**
* 解析括号表达式假定当前 token 是左括号 "("解析中间的表达式并消费右括号 ")" * 解析括号表达式
* <p>
* 该方法假定当前 token 为左括号 "("将解析其内部表达式
* 并断言后续 token 为右括号 ")"
* </p>
* *
* @param ctx 当前解析上下文 * @param ctx 当前解析上下文
* @param token 当前起始 token应为 "(" * @param token 当前起始 token应为 "("
* @return 被括号包围的表达式节点 * @return 被括号包裹的子表达式节点
*/ */
@Override @Override
public ExpressionNode parse(ParserContext ctx, Token token) { public ExpressionNode parse(ParserContext ctx, Token token) {

View File

@ -7,17 +7,20 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet; import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet;
/** /**
* 用于解析标识符identifier的前缀解析器 * {@code IdentifierParselet} 是用于解析标识符表达式的前缀解析器
* 例如变量名函数名等单词形式的表达式 {@code x}{@code count}{@code isValid} * <p>
* 适用于解析如 {@code x}{@code count}{@code isValid} 等变量名或函数名
* 通常出现在表达式的开头部分 AST 中的基础表达式节点之一
* </p>
*/ */
public class IdentifierParselet implements PrefixParselet { public class IdentifierParselet implements PrefixParselet {
/** /**
* 将当前的标识符 Token 解析为 {@link IdentifierNode} 表达式节点 * 解析标识符表达式
* *
* @param ctx 当前解析上下文未使用 * @param ctx 当前语法解析上下文本实现未使用
* @param token 当前标识符 Token * @param token 当前标识符 Token
* @return 一个表示标识符的表达式节点 * @return 构建的 {@link IdentifierNode} 表达式节点
*/ */
@Override @Override
public ExpressionNode parse(ParserContext ctx, Token token) { public ExpressionNode parse(ParserContext ctx, Token token) {

View File

@ -8,34 +8,34 @@ import org.jcnc.snow.compiler.parser.context.TokenStream;
import org.jcnc.snow.compiler.parser.expression.base.InfixParselet; import org.jcnc.snow.compiler.parser.expression.base.InfixParselet;
/** /**
* 用于解析成员访问表达式的中缀解析器例如 {@code object.property} * {@code MemberParselet} 是用于解析成员访问表达式的中缀解析器
* 成员访问是一种紧绑定表达式常用于访问对象的字段或方法 * <p>
* 解析形如 {@code object.property} 的语法结构常用于访问对象字段或方法
* 是一种紧绑定操作优先级通常与函数调用相同
* </p>
*/ */
public class MemberParselet implements InfixParselet { public class MemberParselet implements InfixParselet {
/** /**
* 解析成员访问表达式形如 {@code left.member} * 解析成员访问表达式
* *
* @param ctx 解析上下文 * @param ctx 当前解析上下文
* @param left 已解析的左侧表达式对象 * @param left 已解析的对象表达式左侧
* @return 一个 {@link MemberExpressionNode} 表示成员访问的表达式 * @return {@link MemberExpressionNode} 表示成员访问的表达式节点
*/ */
@Override @Override
public ExpressionNode parse(ParserContext ctx, ExpressionNode left) { public ExpressionNode parse(ParserContext ctx, ExpressionNode left) {
TokenStream ts = ctx.getTokens(); TokenStream ts = ctx.getTokens();
ts.expect("."); // 消费点号 ts.expect("."); // 消费点号
// 接下来应为标识符
String member = ts String member = ts.expectType(TokenType.IDENTIFIER).getLexeme();
.expectType(TokenType.IDENTIFIER)
.getLexeme();
return new MemberExpressionNode(left, member); return new MemberExpressionNode(left, member);
} }
/** /**
* 获取成员访问表达式的优先级 * 获取成员访问操作的优先级
* 与函数调用一样通常具有较高的优先级
* *
* @return {@link Precedence#CALL}表示高优先级 * @return 表达式优先级 {@link Precedence#CALL}
*/ */
@Override @Override
public Precedence getPrecedence() { public Precedence getPrecedence() {

View File

@ -7,17 +7,20 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet; import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet;
/** /**
* 用于解析数字字面量的前缀解析器 * {@code NumberLiteralParselet} 是用于解析数字字面量的前缀解析器
* 例如 {@code 42}{@code 3.14} 等常量数值表达式 * <p>
* 适用于处理如 {@code 42}{@code 3.14} 等整型或浮点型常量表达式
* 通常出现在表达式的起始位置或子表达式内部
* </p>
*/ */
public class NumberLiteralParselet implements PrefixParselet { public class NumberLiteralParselet implements PrefixParselet {
/** /**
* 解析一个数字字面量 Token转换为 {@link NumberLiteralNode} 表达式节点 * 将当前的数字 Token 转换为 {@link NumberLiteralNode} 节点
* *
* @param ctx 当前解析上下文未使用 * @param ctx 当前语法解析上下文此实现未使用
* @param token 当前的数字 Token * @param token 当前的数字字面量 Token
* @return 表示数字字面量的表达式节点 * @return 构建完成的数字表达式节点
*/ */
@Override @Override
public ExpressionNode parse(ParserContext ctx, Token token) { public ExpressionNode parse(ParserContext ctx, Token token) {

View File

@ -12,22 +12,35 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* 基于 Pratt 算法的表达式解析器实现灵活优雅的运算符优先级与结合性处理 * {@code PrattExpressionParser} 是基于 Pratt 算法实现的表达式解析器
* 支持数字字符串标识符分组表达式二元运算函数调用成员访问等 * <p>
* 它支持灵活的运算符优先级控制结合前缀PrefixParselet和中缀InfixParselet解析器
* 可高效解析复杂表达式结构包括
* <ul>
* <li>字面量数字字符串</li>
* <li>标识符</li>
* <li>函数调用成员访问</li>
* <li>带括号的表达式二元运算符</li>
* </ul>
* 本类提供统一注册机制和递归表达式解析入口
* </p>
*/ */
public class PrattExpressionParser implements ExpressionParser { public class PrattExpressionParser implements ExpressionParser {
/** 前缀解析器注册表:按 Token 类型映射 */
private static final Map<String, PrefixParselet> prefixes = new HashMap<>(); private static final Map<String, PrefixParselet> prefixes = new HashMap<>();
/** 中缀解析器注册表:按运算符词素映射 */
private static final Map<String, InfixParselet> infixes = new HashMap<>(); private static final Map<String, InfixParselet> infixes = new HashMap<>();
static { static {
// 注册前缀解析器PrefixParselet // 注册前缀解析器
prefixes.put(TokenType.NUMBER_LITERAL.name(), new NumberLiteralParselet()); prefixes.put(TokenType.NUMBER_LITERAL.name(), new NumberLiteralParselet());
prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet()); prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet());
prefixes.put(TokenType.LPAREN.name(), new GroupingParselet()); prefixes.put(TokenType.LPAREN.name(), new GroupingParselet());
prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet()); // 字符串字面量支持 prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet());
// 注册中缀解析器InfixParselet // 注册中缀解析器
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.PRODUCT, true)); infixes.put("*", new BinaryOperatorParselet(Precedence.PRODUCT, true));
@ -44,10 +57,10 @@ public class PrattExpressionParser implements ExpressionParser {
} }
/** /**
* 解析完整表达式入口方法使用最低优先级启动解析 * 表达式解析入口使用最低优先级启动递归解析
* *
* @param ctx 解析上下文 * @param ctx 当前语法解析上下文
* @return 表达式节点 * @return 表达式抽象语法树节点
*/ */
@Override @Override
public ExpressionNode parse(ParserContext ctx) { public ExpressionNode parse(ParserContext ctx) {
@ -55,11 +68,11 @@ public class PrattExpressionParser implements ExpressionParser {
} }
/** /**
* 根据当前优先级解析表达式 * 根据指定优先级解析表达式
* *
* @param ctx 解析上下文 * @param ctx 当前上下文
* @param prec 当前解析优先级 * @param prec 当前优先级阈值
* @return 表达式节点 * @return 构建完成的表达式节点
*/ */
ExpressionNode parseExpression(ParserContext ctx, Precedence prec) { ExpressionNode parseExpression(ParserContext ctx, Precedence prec) {
Token token = ctx.getTokens().next(); Token token = ctx.getTokens().next();
@ -70,8 +83,7 @@ public class PrattExpressionParser implements ExpressionParser {
ExpressionNode left = prefix.parse(ctx, token); ExpressionNode left = prefix.parse(ctx, token);
while (ctx.getTokens().isAtEnd() && while (ctx.getTokens().isAtEnd() && prec.ordinal() < nextPrecedence(ctx)) {
prec.ordinal() < nextPrecedence(ctx)) {
String lex = ctx.getTokens().peek().getLexeme(); String lex = ctx.getTokens().peek().getLexeme();
InfixParselet infix = infixes.get(lex); InfixParselet infix = infixes.get(lex);
if (infix == null) break; if (infix == null) break;
@ -83,10 +95,10 @@ public class PrattExpressionParser implements ExpressionParser {
} }
/** /**
* 获取下一个中缀运算符的优先级 * 获取下一个中缀解析器的优先级用于判断是否继续解析
* *
* @param ctx 当前解析上下文 * @param ctx 当前上下文
* @return 若存在下一个中缀解析器则返回其优先级 ordinal否则返回 -1 * @return 优先级枚举 ordinal 若无解析器则为 -1
*/ */
private int nextPrecedence(ParserContext ctx) { private int nextPrecedence(ParserContext ctx) {
InfixParselet infix = infixes.get(ctx.getTokens().peek().getLexeme()); InfixParselet infix = infixes.get(ctx.getTokens().peek().getLexeme());

View File

@ -1,27 +1,31 @@
package org.jcnc.snow.compiler.parser.expression; package org.jcnc.snow.compiler.parser.expression;
/** /**
* 表示运算符优先级的枚举类型用于 Pratt 解析算法中比较不同运算符的绑定紧密度 * {@code Precedence} 表示表达式中各类运算符的优先级枚举
* 数值越大优先级越高 * <p>
* 该优先级枚举用于 Pratt 解析器判断运算符的结合顺序
* 枚举顺序即优先级高低数值越大绑定越紧密
* </p>
*/ */
public enum Precedence { public enum Precedence {
/** /**
* 最低优先级通常用于解析入口 * 最低优先级通常用于整个表达式解析的起始入口
*/ */
LOWEST, LOWEST,
/** /**
* 加法减法等二元运算+- * 加法和减法的优先级例如 +-
*/ */
SUM, SUM,
/** /**
* 乘法除法等优先级更高的二元运算*/ * 乘法除法取模等更高优先级的二元运算符例如 */%
*/ */
PRODUCT, PRODUCT,
/** /**
* 函数调用成员访问例如 {@code foo()}{@code obj.prop}绑定最紧密 * 函数调用成员访问等最强绑定例如 foo()obj.prop
*/ */
CALL CALL
} }

View File

@ -7,23 +7,26 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet; import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet;
/** /**
* 用于解析字符串字面量的前缀解析器 * {@code StringLiteralParselet} 是用于解析字符串字面量的前缀解析器
* 会从原始 Token 中提取内容并去除两端的引号 * <p>
* 例如Token {@code "\"hello\""}则解析为 {@code StringLiteralNode("hello")} * 该解析器会将原始字符串 Token带引号转换为 {@link StringLiteralNode}
* 并自动去除词素前后的双引号
* 例如输入 Token {@code "\"hello\""}将被解析为 {@code StringLiteralNode("hello")}
* </p>
*/ */
public class StringLiteralParselet implements PrefixParselet { public class StringLiteralParselet implements PrefixParselet {
/** /**
* 解析字符串字面量 Token去除包裹的引号生成 {@link StringLiteralNode} * 解析字符串字面量 Token
* *
* @param ctx 当前解析上下文未使用 * @param ctx 当前语法解析上下文未使用
* @param token 当前字符串字面量 Token包含带引号的原始文本 * @param token 当前字符串 Token包含引号
* @return 解析后的字符串字面量表达式节点 * @return {@link StringLiteralNode} 表达式节点
*/ */
@Override @Override
public ExpressionNode parse(ParserContext ctx, Token token) { public ExpressionNode parse(ParserContext ctx, Token token) {
String raw = token.getRaw(); // 包含引号的原始字符串例如 "\"Result:\"" String raw = token.getRaw();
String content = raw.substring(1, raw.length() - 1); // 去除前后引号 String content = raw.substring(1, raw.length() - 1);
return new StringLiteralNode(content); return new StringLiteralNode(content);
} }
} }

View File

@ -4,16 +4,28 @@ import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.context.ParserContext;
/** /**
* 表达式解析器接口用于从 {@link ParserContext} 中解析出一个完整的 {@link ExpressionNode} 表达式 * {@code ExpressionParser} 是用于解析表达式的通用接口
* 不同的解析器实现可以支持不同的解析策略 Pratt 解析递归下降等 * <p>
* 实现该接口的解析器应根据 {@link ParserContext} 中提供的 Token
* 构建一个有效的 {@link ExpressionNode} 抽象语法树结构
* </p>
* <p>
* 不同的实现可以采用不同的解析技术
* <ul>
* <li>递归下降Recursive Descent</li>
* <li>Pratt Parser前缀/中缀优先级驱动</li>
* <li>操作符优先表等其他手段</li>
* </ul>
* 通常用于函数体表达式语句条件判断等上下文中的子树构建
* </p>
*/ */
public interface ExpressionParser { public interface ExpressionParser {
/** /**
* 从解析上下文中解析一个表达式 * 从解析上下文中解析并返回一个表达式节点
* *
* @param ctx 当前的解析上下文 * @param ctx 当前语法解析上下文提供 Token 流与辅助状态
* @return 解析后的表达式节点 * @return 构建完成的 {@link ExpressionNode} 表达式 AST 子节点
*/ */
ExpressionNode parse(ParserContext ctx); ExpressionNode parse(ParserContext ctx);
} }

View File

@ -5,26 +5,37 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.Precedence; import org.jcnc.snow.compiler.parser.expression.Precedence;
/** /**
* 中缀解析器接口InfixParselet用于处理中缀表达式的解析逻辑 * {@code InfixParselet} 表示中缀表达式的解析器接口
* 例如二元运算符 {@code a + b}函数调用 {@code f(x)}或成员访问 {@code obj.prop} * <p>
* 实现类需提供解析方法及该表达式的优先级 * 用于构建如 {@code a + b}{@code x * y}{@code f(x)} {@code obj.prop} 等结构
* Pratt 解析器架构中处理中缀操作的关键组件
* </p>
* <p>
* 每个中缀解析器负责
* <ul>
* <li>根据左侧已解析的表达式结合当前运算符继续解析右侧部分</li>
* <li>提供运算符优先级用于判断是否继续嵌套解析</li>
* </ul>
* </p>
*/ */
public interface InfixParselet { public interface InfixParselet {
/** /**
* 解析一个中缀表达式 * 根据已解析的左侧表达式与上下文解析出完整的中缀表达式节点
* *
* @param ctx 解析上下文 * @param ctx 当前解析上下文包含 Token 流状态
* @param left 当前已解析的左侧表达式 * @param left 当前已解析的左表达式节点
* @return 组合后的完整表达式节点 * @return 组合完成的中缀表达式 AST 节点
*/ */
ExpressionNode parse(ParserContext ctx, ExpressionNode left); ExpressionNode parse(ParserContext ctx, ExpressionNode left);
/** /**
* 获取当前中缀表达式的优先级 * 获取当前中缀表达式的解析优先级
* 优先级用于决定运算符的绑定顺序 * <p>
* 用于决定当前操作符是否绑定左右子表达式影响解析树结构
* </p>
* *
* @return 表达式的优先级 * @return 表达式优先级枚举值
*/ */
Precedence getPrecedence(); Precedence getPrecedence();
} }

View File

@ -5,17 +5,29 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.lexer.token.Token; import org.jcnc.snow.compiler.lexer.token.Token;
/** /**
* 前缀解析器接口PrefixParselet用于解析以当前 Token 开头的前缀表达式 * {@code PrefixParselet} 是用于解析前缀表达式的通用接口
* 典型的前缀表达式包括数字字面量标识符括号表达式前缀运算符等 * <p>
* 前缀表达式是以某个词法单元Token作为起始的表达式结构
* 常见类型包括
* <ul>
* <li>数字字面量 {@code 42}</li>
* <li>标识符 {@code foo}</li>
* <li>括号包裹的子表达式 {@code (a + b)}</li>
* <li>前缀一元运算 {@code -x}{@code !flag}</li>
* </ul>
* </p>
* <p>
* 本接口通常用于 Pratt 解析器架构中负责识别语法的起点
* </p>
*/ */
public interface PrefixParselet { public interface PrefixParselet {
/** /**
* 解析一个前缀表达式 * 解析一个以当前 Token 开头的前缀表达式节点
* *
* @param ctx 当前的解析上下文 * @param ctx 当前解析上下文包含 Token 流状态
* @param token 当前的前缀 Token * @param token 当前读取到的前缀 Token
* @return 解析得到的表达式节点 * @return 构建完成的 {@link ExpressionNode} 表达式节点
*/ */
ExpressionNode parse(ParserContext ctx, Token token); ExpressionNode parse(ParserContext ctx, Token token);
} }

View File

@ -6,11 +6,19 @@ import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
/** /**
* 语句解析器工厂类用于根据关键字 "if""loop"返回对应的 {@link StatementParser} 实例 * {@code StatementParserFactory} 是一个语句解析器工厂类
* 所有语句解析器在静态代码块中预先注册 * 用于根据关键字动态选择适当的 {@link StatementParser} 实现
* 若关键字未注册则默认返回 {@link ExpressionStatementParser}空字符串对应 * <p>
* 本类通过静态注册的方式将各类语句解析器绑定到对应的关键字上
* 在语法分析阶段根据当前语句起始词快速获取对应解析逻辑
* </p>
* <p>
* 若传入的关键字未被显式注册将回退使用默认的表达式语句解析器 {@link ExpressionStatementParser}
* </p>
*/ */
public class StatementParserFactory { public class StatementParserFactory {
/** 注册表:语句关键字 -> 对应语句解析器 */
private static final Map<String, StatementParser> registry = new HashMap<>(); private static final Map<String, StatementParser> registry = new HashMap<>();
static { static {
@ -19,15 +27,16 @@ public class StatementParserFactory {
registry.put("if", new IfStatementParser()); registry.put("if", new IfStatementParser());
registry.put("loop", new LoopStatementParser()); registry.put("loop", new LoopStatementParser());
registry.put("return", new ReturnStatementParser()); registry.put("return", new ReturnStatementParser());
// 默认解析器表达式语句
// 默认处理器表达式语句
registry.put("", new ExpressionStatementParser()); registry.put("", new ExpressionStatementParser());
} }
/** /**
* 根据语句关键字获取对应的语句解析器 * 根据关键字查找对应的语句解析器
* *
* @param keyword 语句开头的关键字例如 "if""loop""declare" * @param keyword 当前语句起始关键字 "if""loop"
* @return 对应 {@link StatementParser} 实例若无匹配则返回默认解析器 * @return 匹配 {@link StatementParser} 实例若无匹配则返回默认解析器
*/ */
public static StatementParser get(String keyword) { public static StatementParser get(String keyword) {
return registry.getOrDefault(keyword, registry.get("")); return registry.getOrDefault(keyword, registry.get(""));

View File

@ -8,29 +8,33 @@ import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
/** /**
* 顶层解析器工厂类用于根据文件开头的关键字 {@code module}分发对应的顶层结构解析器 * {@code TopLevelParserFactory} 是一个顶层结构解析器工厂类
* 每种顶层结构模块导入等应有一个专门 {@link TopLevelParser} 实现 * 用于根据源文件起始关键字 {@code module}动态选择相应 {@link TopLevelParser} 实现
* *
* <p>该类采用静态注册机制在类加载时将所有支持的关键字及其解析器实例注册到内部映射表中 * <p>
* 外部通过 {@link #get(String)} 方法获取对应的解析器</p> * 每种顶层结构如模块导入等应实现 {@link TopLevelParser} 接口并在本工厂中静态注册
* 从而在语法分析阶段由 {@link ParserEngine} 根据关键字调用对应的解析器
* </p>
* *
* <p>用于 {@link ParserEngine} 根据关键字动态解析不同语法块</p> * <p>
* 本类采用静态注册表机制在类加载时将支持的关键字与其解析器一一映射并缓存于内部 Map
* 若调用时传入的关键字未注册则返回 {@code null}调用方应自行处理此情况
* </p>
*/ */
public class TopLevelParserFactory { public class TopLevelParserFactory {
// 存储关键字 -> 解析器 实例的映射关系 /** 顶层关键字 -> 顶层结构解析器 的映射表 */
private static final Map<String, TopLevelParser> registry = new HashMap<>(); private static final Map<String, TopLevelParser> registry = new HashMap<>();
static { static {
// 注册模块解析器关键字 "module" 映射到 ModuleParser 实例 // 注册顶层结构解析器
registry.put("module", new ModuleParser()); registry.put("module", new ModuleParser());
} }
/** /**
* 根据顶层关键字获取对应的解析器 * 根据给定关键字获取对应的顶层结构解析器
* *
* @param keyword 顶层结构关键字 "module" * @param keyword 顶层结构关键字 "module"
* @return 对应的 {@link TopLevelParser} 实例未注册则返回 null * @return 对应的 {@link TopLevelParser} 实例关键字未注册则返回 {@code null}
*/ */
public static TopLevelParser get(String keyword) { public static TopLevelParser get(String keyword) {
return registry.get(keyword); return registry.get(keyword);

View File

@ -10,15 +10,30 @@ import org.jcnc.snow.compiler.parser.utils.JsonFormatter;
import java.util.List; import java.util.List;
/** /**
* AST 打印器用于将抽象语法树AST中的节点打印为可读的格式化的多行文本 * {@code ASTPrinter} 是一个抽象语法树AST打印工具类
* 以及将 AST 序列化为 JSON 并美化输出 * 提供用于调试和可视化的格式化文本输出与 JSON 序列化能力
*
* <p>
* 该类支持以缩进方式层级打印任意 AST 节点包括模块函数控制结构等
* 适用于开发阶段调试语法树结构或输出结构化语法分析结果
* </p>
*
* <p>
* 同时支持将 AST 序列化为 JSON 字符串并使用 {@link JsonFormatter} 美化输出
* 便于与外部工具对接或做进一步分析
* </p>
*/ */
public class ASTPrinter { public class ASTPrinter {
/** /**
* 打印整个语法树的节点列表 * 打印整个抽象语法树的节点集合
* *
* @param nodes 要打印的 AST 节点列表通常是顶层模块或语句 * <p>
* 每个节点及其子节点将以多行文本形式打印展示语法树的层级结构
* 该方法通常用于打印完整模块函数集等顶层结构
* </p>
*
* @param nodes 要打印的 AST 节点列表通常为模块或顶层语句列表
*/ */
public static void print(List<Node> nodes) { public static void print(List<Node> nodes) {
for (Node n : nodes) { for (Node n : nodes) {
@ -27,10 +42,15 @@ public class ASTPrinter {
} }
/** /**
* 打印单个节点及其子节点带有缩进格式 * 递归打印单个 AST 节点及其子节点带有格式化缩进
* *
* @param n 要打印的节点 * <p>
* @param indent 当前的缩进层级每层两个空格 * 支持的节点类型包括模块函数声明赋值条件语句循环返回语句表达式语句等
* 若节点类型未显式支持则退回使用 {@code toString()} 方法进行输出
* </p>
*
* @param n 要打印的 AST 节点
* @param indent 当前缩进层级每级对应两个空格
*/ */
private static void print(Node n, int indent) { private static void print(Node n, int indent) {
String pad = " ".repeat(indent); String pad = " ".repeat(indent);
@ -75,7 +95,6 @@ public class ASTPrinter {
} }
} }
} }
case LoopNode( case LoopNode(
StatementNode initializer, ExpressionNode condition, StatementNode update, List<StatementNode> body StatementNode initializer, ExpressionNode condition, StatementNode update, List<StatementNode> body
) -> { ) -> {
@ -95,15 +114,19 @@ public class ASTPrinter {
case ExpressionStatementNode(ExpressionNode expression) -> case ExpressionStatementNode(ExpressionNode expression) ->
System.out.println(pad + expression); System.out.println(pad + expression);
case null, default -> case null, default ->
// 回退如果节点类型不在上述范围内则使用 toString() System.out.println(pad + n); // 回退处理
System.out.println(pad + n);
} }
} }
/** /**
* 序列化整个 AST JSON并输出美化后的字符串 * 打印 AST JSON 表示形式
* *
* @param nodes 要序列化的 AST 节点列表 * <p>
* 本方法将语法树序列化为 JSON 字符串并通过 {@link JsonFormatter#prettyPrint(String)} 方法进行格式化
* 适用于将 AST 结构导出至文件或供其他系统消费
* </p>
*
* @param nodes 要序列化并打印的 AST 节点列表
*/ */
public static void printJson(List<Node> nodes) { public static void printJson(List<Node> nodes) {
String rawJson = ASTJsonSerializer.toJsonString(nodes); String rawJson = ASTJsonSerializer.toJsonString(nodes);