增加Java Doc

This commit is contained in:
Luke 2025-04-23 16:50:46 +08:00
parent cd402653cb
commit ff51636fe7
50 changed files with 766 additions and 162 deletions

View File

@ -1,26 +1,44 @@
package org.jcnc.snow.compiler;
/**
* JSON 格式化工具类
* 提供将紧凑 JSON 字符串美化为带缩进和换行的易读格式的方法
*/
public class JsonFormatter {
/**
* 对一个紧凑的 JSON 字符串进行缩进美化
* @param json 紧凑的 JSON
* @return 带换行和缩进的 JSON
* 例如
* <pre>{@code
* {"a":1,"b":[2,3]}
* {
* "a": 1,
* "b": [
* 2,
* 3
* ]
* }
* }</pre>
*
* @param json 紧凑的 JSON 字符串
* @return 格式化后的 JSON 字符串带有缩进与换行
*/
public static String prettyPrint(String json) {
StringBuilder sb = new StringBuilder();
int indent = 0;
boolean inQuotes = false;
for (int i = 0; i < json.length(); i++) {
char c = json.charAt(i);
// 切换引号状态忽略转义的 \"
// 检查是否进入或退出字符串忽略转义的引号
if (c == '"' && (i == 0 || json.charAt(i - 1) != '\\')) {
inQuotes = !inQuotes;
sb.append(c);
} else if (!inQuotes) {
switch (c) {
case '{', '[':
sb.append(c)
.append('\n');
sb.append(c).append('\n');
indent++;
appendIndent(sb, indent);
break;
@ -31,8 +49,7 @@ public class JsonFormatter {
sb.append(c);
break;
case ',':
sb.append(c)
.append('\n');
sb.append(c).append('\n');
appendIndent(sb, indent);
break;
case ':':
@ -44,14 +61,21 @@ public class JsonFormatter {
}
}
} else {
// 字符串内原样输出
// 字符串内原样输出
sb.append(c);
}
}
return sb.toString();
}
/**
* 向字符串构建器追加指定层级的缩进
*
* @param sb 输出目标
* @param indent 缩进层级每层为两个空格
*/
private static void appendIndent(StringBuilder sb, int indent) {
sb.append(" ".repeat(Math.max(0, indent))); // 两个空格
sb.append(" ".repeat(Math.max(0, indent)));
}
}

View File

@ -10,31 +10,48 @@ import java.util.ArrayList;
import java.util.List;
/**
* 解析引擎入口循环读取顶层块直到 EOF自动跳过空行NEWLINE
* 解析器主引擎负责驱动顶层结构 module解析
* 它会循环处理每个顶层语法块直到遇到 EOF
* 同时自动跳过空行NEWLINE以增强容错性
*/
public class ParserEngine {
private final ParserContext ctx;
/**
* 构造解析器引擎
*
* @param ctx 解析上下文封装了 Token 流等解析状态
*/
public ParserEngine(ParserContext ctx) {
this.ctx = ctx;
}
/**
* 启动解析流程返回顶层节点列表模块导入等
*
* @return 所有解析出的 AST 顶层节点
*/
public List<Node> parse() {
List<Node> nodes = new ArrayList<>();
TokenStream ts = ctx.getTokens();
while (ts.isAtEnd()) {
// 跳过空行NEWLINE
// 跳过空行
if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
continue;
}
// 根据当前关键字分发顶层解析器
String lex = ts.peek().getLexeme();
TopLevelParser p = TopLevelParserFactory.get(lex);
if (p == null) {
TopLevelParser parser = TopLevelParserFactory.get(lex);
if (parser == null) {
throw new IllegalStateException("Unexpected top-level token: " + lex);
}
nodes.add(p.parse(ctx));
nodes.add(parser.parse(ctx));
}
return nodes;
}
}

View File

@ -4,8 +4,16 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.ast.Node;
/**
* 顶层解析器接口 moduleimport
* 顶层结构解析器接口用于解析模块级别的语法结构 {@code module}{@code import}
* 所有顶层解析器应实现该接口
*/
public interface TopLevelParser {
/**
* 从解析上下文中解析一个顶层语法结构
*
* @param ctx 当前解析上下文
* @return 表示顶层结构的 AST 节点
*/
Node parse(ParserContext ctx);
}

View File

@ -2,8 +2,19 @@ package org.jcnc.snow.compiler.parser.ast;
import java.util.List;
/**
* ASTJsonSerializer 是一个用于将抽象语法树AST序列化为 JSON 字符串的工具类
* 该类通过访问不同类型的语法树节点将其结构以标准 JSON 格式输出适用于调试可视化或进一步处理
*/
public class ASTJsonSerializer {
/**
* 将语法树节点列表序列化为 JSON 字符串
*
* @param ast 抽象语法树的节点列表
* @return 表示整个 AST JSON 字符串
*/
public static String toJsonString(List<Node> ast) {
StringBuilder sb = new StringBuilder();
sb.append("[");
@ -189,8 +200,12 @@ public class ASTJsonSerializer {
+ "}";
}
/** 把 Java 字符串内容转成 JSON 字符串常量(加双引号并转义) */
private static String quote(String s) {
/**
* 将字符串转换为合法的 JSON 字符串常量添加双引号并进行必要的转义
*
* @param s 原始字符串
* @return 转义并加双引号的 JSON 字符串
*/ private static String quote(String s) {
StringBuilder sb = new StringBuilder();
sb.append("\"");
for (char c : s.toCharArray()) {

View File

@ -3,17 +3,30 @@ package org.jcnc.snow.compiler.parser.ast;
import java.util.List;
/**
* 一个简单的 AST 打印器将所有节点格式化成可读的多行文本
* AST 打印器用于将抽象语法树AST中的节点打印为可读的格式化的多行文本
* 每个节点根据其类型和结构打印相应的缩进层级便于人类阅读和调试
*/
public class ASTPrinter {
/**
* 打印整个语法树的节点列表
*
* @param nodes 要打印的 AST 节点列表通常是顶层模块或语句
*/
public static void print(List<Node> nodes) {
for (Node n : nodes) {
print(n, 0);
}
}
/**
* 打印单个节点及其子节点带有缩进格式
*
* @param n 要打印的节点
* @param indent 当前的缩进层级每层两个空格
*/
private static void print(Node n, int indent) {
String pad = " ".repeat(indent);
String pad = " ".repeat(indent); // 缩进字符串
switch (n) {
case ModuleNode m -> {
@ -65,9 +78,10 @@ public class ASTPrinter {
}
case ReturnNode r -> System.out.println(pad + "return" +
r.getExpression().map(e -> " " + e).orElse(""));
case ExpressionStatementNode(ExpressionNode expression) -> System.out.println(pad + expression);
case ExpressionStatementNode(ExpressionNode expression) ->
System.out.println(pad + expression);
case null, default ->
// 回退直接调 toString()
// 回退如果节点类型不在上述范围内则使 toString()
System.out.println(pad + n);
}
}

View File

@ -1,10 +1,18 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 赋值语句节点例如 x = expr
* 表示赋值语句的 AST抽象语法树节点例如 {@code x = expr}
*
* @param variable 赋值语句左侧的变量名
* @param value 赋值语句右侧的表达式节点表示要赋给变量的值
*/
public record AssignmentNode(String variable, ExpressionNode value) implements StatementNode {
/**
* 返回赋值语句的字符串表示形式例如 {@code x = y + 1}
*
* @return 表示赋值语句的字符串
*/
@Override
public String toString() {
return variable + " = " + value;

View File

@ -1,11 +1,21 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 二元运算表达式节点 a + b
* 表示二元运算表达式的 AST 节点例如 {@code a + b}
* 该节点包含一个左操作数一个操作符字符串和一个右操作数
*
* @param left 表达式左侧的子表达式
* @param operator 运算符字符串例如 "+""-""*""/"
* @param right 表达式右侧的子表达式
*/
public record BinaryExpressionNode(ExpressionNode left, String operator,
ExpressionNode right) implements ExpressionNode {
/**
* 返回该二元表达式的字符串表示形式例如 {@code a + b}
*
* @return 表达式的字符串形式
*/
@Override
public String toString() {
return left + " " + operator + " " + right;

View File

@ -3,10 +3,19 @@ package org.jcnc.snow.compiler.parser.ast;
import java.util.List;
/**
* 函数调用表达式节点callee(args)
* 表示函数调用表达式的 AST 节点例如 {@code foo(arg1, arg2)}
* 包含一个被调用的表达式callee和一个参数列表arguments
*
* @param callee 被调用的表达式通常是一个标识符或成员访问表达式
* @param arguments 函数调用的参数列表每个参数都是一个表达式节点
*/
public record CallExpressionNode(ExpressionNode callee, List<ExpressionNode> arguments) implements ExpressionNode {
/**
* 返回函数调用表达式的字符串表示形式例如 {@code foo(x, y)}
*
* @return 表达式的字符串形式
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();

View File

@ -3,20 +3,45 @@ package org.jcnc.snow.compiler.parser.ast;
import java.util.Optional;
/**
* declare 语句节点声明变量可选初始化表达式
* 表示变量声明语句的 AST 节点例如 {@code int x = 5;}
* 该节点包含变量名称类型以及可选的初始化表达式
*/
public class DeclarationNode implements StatementNode {
private final String name;
private final String type;
private final Optional<ExpressionNode> initializer;
/**
* 构造一个变量声明节点
*
* @param name 变量的名称
* @param type 变量的类型例如 "int", "string"
* @param initializer 可选的初始化表达式若为 null 表示未初始化
*/
public DeclarationNode(String name, String type, ExpressionNode initializer) {
this.name = name;
this.type = type;
this.initializer = Optional.ofNullable(initializer);
}
/**
* 获取变量名称
*
* @return 变量名字符串
*/
public String getName() { return name; }
/**
* 获取变量类型
*
* @return 变量类型字符串
*/
public String getType() { return type; }
/**
* 获取可选的初始化表达式
*
* @return 包含初始化表达式的 Optional 对象可能为空
*/
public Optional<ExpressionNode> getInitializer() { return initializer; }
}

View File

@ -1,6 +1,8 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 标记所有表达式节点
* 表示抽象语法树中的表达式节点类型
* 所有具体的表达式如常量变量运算函数调用等都应实现该接口
* 这是一个标记接口marker interface用于统一处理所有表达式节点
*/
public interface ExpressionNode extends Node {}

View File

@ -1,7 +1,10 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 单独的表达式语句节点如函数调用赋值等
* 表示一条独立的表达式语句例如 {@code foo();} {@code x = 1;}
* 该节点将一个表达式作为语句出现
*
* @param expression 表达式内容通常是函数调用或赋值操作
*/
public record ExpressionStatementNode(ExpressionNode expression) implements StatementNode {
}

View File

@ -3,7 +3,13 @@ package org.jcnc.snow.compiler.parser.ast;
import java.util.List;
/**
* 函数定义节点包含函数名参数列表返回类型和函数体语句列表
* 表示函数定义的 AST 节点包含函数名参数返回类型和函数体
* 示例{@code int add(int a, int b) { return a + b; }}
*
* @param name 函数名称
* @param parameters 参数列表每个参数是一个 {@link ParameterNode}
* @param returnType 返回值类型表示函数的返回类型例如 "int""void"
* @param body 函数体由若干语句组成的列表
*/
public record FunctionNode(String name, List<ParameterNode> parameters, String returnType,
List<StatementNode> body) implements Node {

View File

@ -1,9 +1,17 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 标识符表达式节点
* 表示标识符的表达式节点例如变量名 {@code x}函数名 {@code foo}
*
* @param name 标识符的名称
*/
public record IdentifierNode(String name) implements ExpressionNode {
/**
* 返回标识符的字符串表示形式即其名称
*
* @return 标识符名称字符串
*/
@Override
public String toString() {
return name;

View File

@ -3,7 +3,12 @@ package org.jcnc.snow.compiler.parser.ast;
import java.util.List;
/**
* if 语句节点包含条件表达式和 then 分支语句列表
* 表示 if 语句的 AST 节点
* 包含一个条件表达式和一个 then 分支的语句列表不包含 else 分支
* 示例{@code if (x > 0) { print(x); }}
*
* @param condition 条件表达式控制是否执行 then 分支
* @param thenBranch 条件为真时执行的语句列表
*/
public record IfNode(ExpressionNode condition, List<StatementNode> thenBranch) implements StatementNode {
}

View File

@ -1,7 +1,11 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* import 语句节点仅保存被导入模块名
* 表示 import 语句的 AST 节点
* 该节点仅包含被导入模块的名称
* 示例{@code import my.module;}
*
* @param moduleName 被导入的模块名称
*/
public record ImportNode(String moduleName) implements Node {
}

View File

@ -3,7 +3,19 @@ package org.jcnc.snow.compiler.parser.ast;
import java.util.List;
/**
* loop 语句节点包含初始化语句条件表达式更新语句和循环体
* 表示循环语句loop AST 节点结构类似于传统的 for 循环
* 包含初始化语句循环条件更新语句和循环体
* 示例
* {@code
* for (int i = 0; i < 10; i++) {
* print(i);
* }
* }
*
* @param initializer 循环开始前执行的初始化语句
* @param condition 每次迭代前都会评估的条件表达式
* @param update 每次迭代结束时执行的更新语句
* @param body 循环体内的语句列表
*/
public record LoopNode(StatementNode initializer, ExpressionNode condition, StatementNode update,
List<StatementNode> body) implements StatementNode {

View File

@ -1,9 +1,19 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 成员访问表达式节点 obj.prop
* 表示成员访问的表达式节点例如 {@code obj.prop}
* 包含一个对象表达式和成员名称
*
* @param object 表达式左侧的对象部分通常是一个标识符或更复杂的表达式
* @param member 成员名称表示要访问的字段或方法
*/
public record MemberExpressionNode(ExpressionNode object, String member) implements ExpressionNode {
/**
* 返回成员访问表达式的字符串表示形式例如 {@code obj.prop}
*
* @return 成员访问表达式的字符串形式
*/
@Override
public String toString() {
return object + "." + member;

View File

@ -1,16 +1,29 @@
// File: src/main/java/org/jcnc/snow/compiler/parser/ast/ModuleNode.java
package org.jcnc.snow.compiler.parser.ast;
import java.util.List;
import java.util.StringJoiner;
/**
* 模块定义节点包含模块名import 列表和函数列表
* 表示模块定义的 AST 节点
* 一个模块通常由模块名导入语句列表和函数定义列表组成
* 示例结构
* {@code
* module my.module {
* import other.module;
* fun foo() { ... }
* }
* }
*
* @param name 模块名称
* @param imports 模块导入列表每个导入是一个 {@link ImportNode}
* @param functions 模块中的函数列表每个函数是一个 {@link FunctionNode}
*/
public record ModuleNode(String name, List<ImportNode> imports, List<FunctionNode> functions) implements Node {
/**
* 返回模块的字符串表示便于调试和打印
* 返回模块节点的字符串表示形式包含模块名导入模块列表和函数列表
*
* @return 模块的简洁字符串表示用于调试和日志输出
*/
@Override
public String toString() {
@ -23,7 +36,7 @@ public record ModuleNode(String name, List<ImportNode> imports, List<FunctionNod
funcJoiner.add(fn.name());
}
return "Module(name=" + name
+ ", imports=[" + importJoiner.toString() + "]"
+ ", functions=[" + funcJoiner.toString() + "])";
+ ", imports=[" + importJoiner + "]"
+ ", functions=[" + funcJoiner + "])";
}
}

View File

@ -1,6 +1,8 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 所有 AST 节点的标记接口
* 所有抽象语法树AST节点的标记接口
* 所有具体的语法节点类型如表达式语句模块函数等都应实现该接口
* 该接口本身不定义任何方法仅用于类型标识和统一处理
*/
public interface Node {}

View File

@ -1,10 +1,18 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 数字字面量表达式节点
* 表示数字字面量的表达式节点例如 {@code 42} {@code 3.14}
* 该节点直接保存字面量的字符串形式便于统一处理整数和浮点数
*
* @param value 数字的字符串表示形式
*/
public record NumberLiteralNode(String value) implements ExpressionNode {
/**
* 返回数字字面量的字符串形式
*
* @return 字面量的原始字符串值
*/
@Override
public String toString() {
return value;

View File

@ -1,12 +1,18 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 函数参数节点包含参数名和类型
* 表示函数参数的 AST 节点包含参数名称和类型
* 示例 {@code int add(int a, int b)} {@code a:int} {@code b:int} 都是参数节点
*
* @param name 参数名称
* @param type 参数类型例如 "int""string"
*/
public record ParameterNode(String name, String type) implements Node {
/**
* 返回参数的字符串表示例如 "num1:int"便于列表打印
* 返回参数的字符串表示形式格式为 {@code name:type} {@code num:int}
*
* @return 参数的字符串表示适用于调试或打印参数列表
*/
@Override
public String toString() {

View File

@ -3,14 +3,31 @@ package org.jcnc.snow.compiler.parser.ast;
import java.util.Optional;
/**
* return 语句节点可选返回表达式
* 表示 return 语句的 AST 节点支持可选的返回值表达式
* 示例
* <ul>
* <li>{@code return;}</li>
* <li>{@code return x + 1;}</li>
* </ul>
*/
public class ReturnNode implements StatementNode {
private final Optional<ExpressionNode> expression;
/**
* 构造一个 return 语句节点
*
* @param expression 可选的返回表达式若为 null 则表示无返回值
*/
public ReturnNode(ExpressionNode expression) {
this.expression = Optional.ofNullable(expression);
}
public Optional<ExpressionNode> getExpression() { return expression; }
/**
* 获取返回表达式
*
* @return 表示返回值的可选表达式如果没有则为 {@code Optional.empty()}
*/
public Optional<ExpressionNode> getExpression() {
return expression;
}
}

View File

@ -1,6 +1,8 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 标记所有语句节点
* 表示语法树中的语句节点类型
* 所有语句类如声明赋值ifloopreturn 都应实现该接口
* 这是一个标记接口marker interface用于统一处理所有语句节点
*/
public interface StatementNode extends Node {}

View File

@ -1,15 +1,18 @@
package org.jcnc.snow.compiler.parser.ast;
/**
* 字符串字面量表达式节点 "hello"
* 表示字符串字面量的表达式节点例如 {@code "hello"}
* 字面量值不包含引号仅在打印时添加
*
* @param value 不包含引号的字符串内容
* @param value 字符串内容不包括两端引号
*/
public record StringLiteralNode(String value) implements ExpressionNode {
/**
* 打印时在两端加上引号
* 例如 value = Result: toString() 返回 "Result:"
* 返回带引号的字符串表示形式适用于调试和打印
* 例如如果 {@code value = Result:} {@code toString()} 返回 {@code "Result:"}
*
* @return 添加双引号后的字符串字面量
*/
@Override
public String toString() {

View File

@ -1,9 +1,16 @@
package org.jcnc.snow.compiler.parser.context;
/**
* 解析时抛出的异常用于语法错误报告
* 表示解析过程中发生的异常通常用于报告语法错误
* 在语法分析器发现非法语法或无法处理的结构时抛出
*/
public class ParseException extends RuntimeException {
/**
* 构造一个解析异常实例并提供错误消息
*
* @param message 错误描述信息用于指出语法错误的详情
*/
public ParseException(String message) {
super(message);
}

View File

@ -4,15 +4,26 @@ import org.jcnc.snow.compiler.lexer.token.Token;
import java.util.List;
/**
* Parser 共享上下文包含 TokenStream可扩展错误收集符号表等功能
* 表示解析器的共享上下文封装了当前的 {@link TokenStream}
* 后续还可以扩展为包含错误收集器符号表作用域信息等
*/
public class ParserContext {
private final TokenStream tokens;
/**
* 构造解析上下文并包装传入的词法单元列表为 {@link TokenStream}
*
* @param tokens 词法分析器生成的 token 列表
*/
public ParserContext(List<Token> tokens) {
this.tokens = new TokenStream(tokens);
}
/**
* 获取当前的 Token 用于驱动语法解析过程
*
* @return token 流实例
*/
public TokenStream getTokens() {
return tokens;
}

View File

@ -6,18 +6,29 @@ import org.jcnc.snow.compiler.lexer.token.TokenType;
import java.util.List;
/**
* 封装 Token 列表并维护当前位置提供 peek/next/match/expect 等操作
* 现在新增 peek(offset) 以便在解析赋值时向前看一个 Token
* 封装 Token 列表并维护当前位置用于解析器消费 Token 的工具类
* 提供常见的操作如 {@code peek}查看{@code next}消费{@code match}匹配
* {@code expect}断言支持向前查看多个 Token 以实现前瞻解析
*/
public class TokenStream {
private final List<Token> tokens;
private int pos = 0;
/**
* 构造 Token 流对象
*
* @param tokens 从词法分析器生成的 Token 列表
*/
public TokenStream(List<Token> tokens) {
this.tokens = tokens;
}
/** 向前 offset 个位置的 Token不消费 */
/**
* 向前查看指定偏移量的 Token不消费
*
* @param offset 相对于当前位置的偏移量例如 0 表示当前位置
* @return 对应位置的 Token如果越界则返回 EOF Token
*/
public Token peek(int offset) {
int idx = pos + offset;
if (idx >= tokens.size()) {
@ -26,19 +37,32 @@ public class TokenStream {
return tokens.get(idx);
}
/** 默认 peek(0) */
/**
* 查看当前位置的 Token等价于 {@code peek(0)}
*
* @return 当前 Token
*/
public Token peek() {
return peek(0);
}
/** 消费并返回当前 Token */
/**
* 消费并返回当前位置的 Token内部位置前移
*
* @return 当前的 Token
*/
public Token next() {
Token t = peek();
pos++;
return t;
}
/** 如果当前词素等于 lexeme则消费并返回 true */
/**
* 如果当前 Token 的词素等于指定字符串则消费该 Token 并返回 true
*
* @param lexeme 要匹配的词素
* @return 是否成功匹配
*/
public boolean match(String lexeme) {
if (peek().getLexeme().equals(lexeme)) {
next();
@ -47,7 +71,13 @@ public class TokenStream {
return false;
}
/** 断言 lexeme否则抛错 */
/**
* 断言当前 Token 的词素等于指定字符串否则抛出 {@link ParseException}
*
* @param lexeme 期望的词素
* @return 匹配的 Token
* @throws ParseException 如果词素不匹配
*/
public Token expect(String lexeme) {
Token t = peek();
if (!t.getLexeme().equals(lexeme)) {
@ -59,7 +89,13 @@ public class TokenStream {
return next();
}
/** 断言类型,否则抛错 */
/**
* 断言当前 Token 的类型等于指定类型否则抛出 {@link ParseException}
*
* @param type 期望的 Token 类型
* @return 匹配的 Token
* @throws ParseException 如果类型不匹配
*/
public Token expectType(TokenType type) {
Token t = peek();
if (t.getType() != type) {
@ -71,6 +107,11 @@ public class TokenStream {
return next();
}
/**
* 判断是否还未到达文件结尾EOF
*
* @return 如果当前位置的 Token 不是 EOF则返回 true
*/
public boolean isAtEnd() {
return peek().getType() != TokenType.EOF;
}

View File

@ -6,29 +6,48 @@ import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.parser.context.ParserContext;
/**
* 解析二元运算符表达式支持左/右结合
* 表示中缀二元运算符的解析规则parselet用于解析如 {@code a + b} 的表达式结构
* 支持设置运算符的优先级和结合性左结合或右结合
*/
public class BinaryOperatorParselet implements InfixParselet {
private final Precedence precedence;
private final boolean leftAssoc;
/**
* 构造二元运算符的解析器
*
* @param precedence 运算符的优先级
* @param leftAssoc 是否是左结合true 表示左结合false 表示右结合
*/
public BinaryOperatorParselet(Precedence precedence, boolean leftAssoc) {
this.precedence = precedence;
this.leftAssoc = leftAssoc;
}
/**
* 解析一个中缀表达式
*
* @param ctx 解析上下文包含 Token 流等信息
* @param left 当前已解析出的左表达式
* @return 一个新的 {@link BinaryExpressionNode} 表达式节点
*/
@Override
public ExpressionNode parse(ParserContext ctx, ExpressionNode left) {
Token op = ctx.getTokens().next();
Token op = ctx.getTokens().next(); // 获取当前运算符 Token
int prec = precedence.ordinal();
// 若左结合则右侧优先级减一
// 如果是左结合运算符右表达式的优先级应减一右边绑定更紧
ExpressionNode right = new PrattExpressionParser().parseExpression(
ctx,
leftAssoc ? Precedence.values()[prec] : Precedence.values()[prec - 1]
ctx,
leftAssoc ? Precedence.values()[prec] : Precedence.values()[prec - 1]
);
return new BinaryExpressionNode(left, op.getLexeme(), right);
}
/**
* 返回该运算符的优先级
*
* @return 当前运算符的优先级枚举
*/
@Override
public Precedence getPrecedence() {
return precedence;

View File

@ -8,23 +8,40 @@ import java.util.ArrayList;
import java.util.List;
/**
* 解析函数调用left ( args )
* 表示函数调用的解析规则parselet用于解析形如 {@code foo(arg1, arg2)} 的调用表达式
* 函数调用的结构为已解析的 callee 表达式后接一对圆括号和参数列表
*/
public class CallParselet implements InfixParselet {
/**
* 解析函数调用表达式形如 {@code callee(args...)}
*
* @param ctx 解析上下文包含 Token 流等信息
* @param left 已解析的函数名或调用目标 callee 表达式
* @return 生成的 {@link CallExpressionNode} 表达式节点
*/
@Override
public ExpressionNode parse(ParserContext ctx, ExpressionNode left) {
// 已知 peek "("
ctx.getTokens().next(); // consume "("
ctx.getTokens().next(); // 消费 "("
List<ExpressionNode> args = new ArrayList<>();
// 如果不是空参数列表
if (!ctx.getTokens().peek().getLexeme().equals(")")) {
do {
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 {@link Precedence#CALL} 表示函数调用的绑定紧密度
*/
@Override
public Precedence getPrecedence() {
return Precedence.CALL;

View File

@ -4,8 +4,16 @@ import org.jcnc.snow.compiler.parser.ast.ExpressionNode;
import org.jcnc.snow.compiler.parser.context.ParserContext;
/**
* 表达式解析接口
* 表达式解析器接口用于从 {@link ParserContext} 中解析出一个完整的 {@link ExpressionNode} 表达式
* 不同的解析器实现可以支持不同的解析策略 Pratt 解析递归下降等
*/
public interface ExpressionParser {
/**
* 从解析上下文中解析一个表达式
*
* @param ctx 当前的解析上下文
* @return 解析后的表达式节点
*/
ExpressionNode parse(ParserContext ctx);
}

View File

@ -5,9 +5,18 @@ import org.jcnc.snow.compiler.parser.ast.ExpressionNode;
import org.jcnc.snow.compiler.parser.context.ParserContext;
/**
* 解析括号表达式 ( expr )
* 解析圆括号包裹的表达式例如 {@code (a + b)}
* 用于提升子表达式的优先级使其在整体表达式中优先计算
*/
public class GroupingParselet implements PrefixParselet {
/**
* 解析括号表达式假定当前 token 是左括号 "("解析中间的表达式并消费右括号 ")"
*
* @param ctx 当前解析上下文
* @param token 当前起始 token应为 "("
* @return 被括号包围的表达式节点
*/
@Override
public ExpressionNode parse(ParserContext ctx, Token token) {
ExpressionNode expr = new PrattExpressionParser().parse(ctx);

View File

@ -6,9 +6,18 @@ import org.jcnc.snow.compiler.parser.ast.ExpressionNode;
import org.jcnc.snow.compiler.parser.context.ParserContext;
/**
* 解析标识符
* 用于解析标识符identifier的前缀解析器
* 例如变量名函数名等单词形式的表达式 {@code x}{@code count}{@code isValid}
*/
public class IdentifierParselet implements PrefixParselet {
/**
* 将当前的标识符 Token 解析为 {@link IdentifierNode} 表达式节点
*
* @param ctx 当前解析上下文未使用
* @param token 当前的标识符 Token
* @return 一个表示标识符的表达式节点
*/
@Override
public ExpressionNode parse(ParserContext ctx, Token token) {
return new IdentifierNode(token.getLexeme());

View File

@ -4,9 +4,26 @@ import org.jcnc.snow.compiler.parser.ast.ExpressionNode;
import org.jcnc.snow.compiler.parser.context.ParserContext;
/**
* 中缀解析器接口处理二元运算函数调用成员访问等
* 中缀解析器接口InfixParselet用于处理中缀表达式的解析逻辑
* 例如二元运算符 {@code a + b}函数调用 {@code f(x)}或成员访问 {@code obj.prop}
* 实现类需提供解析方法及该表达式的优先级
*/
public interface InfixParselet {
/**
* 解析一个中缀表达式
*
* @param ctx 解析上下文
* @param left 当前已解析的左侧表达式
* @return 组合后的完整表达式节点
*/
ExpressionNode parse(ParserContext ctx, ExpressionNode left);
/**
* 获取当前中缀表达式的优先级
* 优先级用于决定运算符的绑定顺序
*
* @return 表达式的优先级
*/
Precedence getPrecedence();
}

View File

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

View File

@ -6,9 +6,18 @@ import org.jcnc.snow.compiler.parser.ast.ExpressionNode;
import org.jcnc.snow.compiler.parser.context.ParserContext;
/**
* 解析数字字面量
* 用于解析数字字面量的前缀解析器
* 例如 {@code 42}{@code 3.14} 等常量数值表达式
*/
public class NumberLiteralParselet implements PrefixParselet {
/**
* 解析一个数字字面量 Token转换为 {@link NumberLiteralNode} 表达式节点
*
* @param ctx 当前解析上下文未使用
* @param token 当前的数字 Token
* @return 表示数字字面量的表达式节点
*/
@Override
public ExpressionNode parse(ParserContext ctx, Token token) {
return new NumberLiteralNode(token.getLexeme());

View File

@ -9,45 +9,61 @@ import java.util.HashMap;
import java.util.Map;
/**
* Pratt 算法实现的表达式解析器支持算术比较调用成员访问字面量等
* 基于 Pratt 算法的表达式解析器实现灵活优雅的运算符优先级与结合性处理
* 支持数字字符串标识符分组表达式二元运算函数调用成员访问等
*/
public class PrattExpressionParser implements ExpressionParser {
private static final Map<String, PrefixParselet> prefixes = new HashMap<>();
private static final Map<String, InfixParselet> infixes = new HashMap<>();
static {
// 注册前缀 parselet
// 注册前缀解析器PrefixParselet
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.STRING_LITERAL.name(), new StringLiteralParselet()); // 字符串字面量支持
// 注册中缀 parselet保持原样
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));
// 注册中缀解析器InfixParselet
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.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 CallParselet());
infixes.put(".", new MemberParselet());
}
/**
* 解析完整表达式入口方法使用最低优先级启动解析
*
* @param ctx 解析上下文
* @return 表达式节点
*/
@Override
public ExpressionNode parse(ParserContext ctx) {
return parseExpression(ctx, Precedence.LOWEST);
}
/**
* 根据当前优先级解析表达式
*
* @param ctx 解析上下文
* @param prec 当前解析优先级
* @return 表达式节点
*/
ExpressionNode parseExpression(ParserContext ctx, Precedence prec) {
Token token = ctx.getTokens().next();
PrefixParselet prefix = prefixes.get(token.getType().name());
if (prefix == null) {
throw new IllegalStateException("No prefix for " + token.getType());
throw new IllegalStateException("No prefix parselet registered for token type: " + token.getType());
}
ExpressionNode left = prefix.parse(ctx, token);
while (ctx.getTokens().isAtEnd() &&
@ -55,11 +71,19 @@ public class PrattExpressionParser implements ExpressionParser {
String lex = ctx.getTokens().peek().getLexeme();
InfixParselet infix = infixes.get(lex);
if (infix == null) break;
left = infix.parse(ctx, left);
}
return left;
}
/**
* 获取下一个中缀运算符的优先级
*
* @param ctx 当前解析上下文
* @return 若存在下一个中缀解析器则返回其优先级 ordinal否则返回 -1
*/
private int nextPrecedence(ParserContext ctx) {
InfixParselet infix = infixes.get(ctx.getTokens().peek().getLexeme());
return infix != null ? infix.getPrecedence().ordinal() : -1;

View File

@ -1,11 +1,27 @@
package org.jcnc.snow.compiler.parser.expression;
/**
* 运算符优先级枚举用于 Pratt 算法比较
* 表示运算符优先级的枚举类型用于 Pratt 解析算法中比较不同运算符的绑定紧密度
* 数值越大优先级越高
*/
public enum Precedence {
LOWEST, // 最低
SUM, // + -
PRODUCT, // * /
CALL // 函数调用成员访问
/**
* 最低优先级通常用于解析入口
*/
LOWEST,
/**
* 加法减法等二元运算+-
*/
SUM,
/**
* 乘法除法等优先级更高的二元运算*/
*/
PRODUCT,
/**
* 函数调用成员访问例如 {@code foo()}{@code obj.prop}绑定最紧密
*/
CALL
}

View File

@ -5,8 +5,17 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.lexer.token.Token;
/**
* 前缀解析器接口处理数字字面量标识符括号等前缀
* 前缀解析器接口PrefixParselet用于解析以当前 Token 开头的前缀表达式
* 典型的前缀表达式包括数字字面量标识符括号表达式前缀运算符等
*/
public interface PrefixParselet {
/**
* 解析一个前缀表达式
*
* @param ctx 当前的解析上下文
* @param token 当前的前缀 Token
* @return 解析得到的表达式节点
*/
ExpressionNode parse(ParserContext ctx, Token token);
}

View File

@ -6,15 +6,23 @@ import org.jcnc.snow.compiler.parser.ast.StringLiteralNode;
import org.jcnc.snow.compiler.parser.context.ParserContext;
/**
* 解析字符串字面量前缀并去掉两端的引号
* 用于解析字符串字面量的前缀解析器
* 会从原始 Token 中提取内容并去除两端的引号
* 例如Token {@code "\"hello\""}则解析为 {@code StringLiteralNode("hello")}
*/
public class StringLiteralParselet implements PrefixParselet {
/**
* 解析字符串字面量 Token去除包裹的引号生成 {@link StringLiteralNode}
*
* @param ctx 当前解析上下文未使用
* @param token 当前字符串字面量 Token包含带引号的原始文本
* @return 解析后的字符串字面量表达式节点
*/
@Override
public ExpressionNode parse(ParserContext ctx, Token token) {
// token.getRaw() 包含原始 "\"Result:\""
String raw = token.getRaw();
// 去掉首尾引号
String content = raw.substring(1, raw.length() - 1);
String raw = token.getRaw(); // 包含引号的原始字符串例如 "\"Result:\""
String content = raw.substring(1, raw.length() - 1); // 去除前后引号
return new StringLiteralNode(content);
}
}

View File

@ -1,24 +1,34 @@
package org.jcnc.snow.compiler.parser.factory;
import org.jcnc.snow.compiler.parser.statement.*;
import java.util.Map;
import java.util.HashMap;
/**
* 语句解析器工厂关键字 对应 StatementParser
* 语句解析器工厂类用于根据关键字 "if""loop"返回对应的 {@link StatementParser} 实例
* 所有语句解析器在静态代码块中预先注册
* 若关键字未注册则默认返回 {@link ExpressionStatementParser}空字符串对应
*/
public class StatementParserFactory {
private static final Map<String, StatementParser> registry = new HashMap<>();
static {
// 注册各类语句解析器
registry.put("declare", new DeclarationStatementParser());
registry.put("if", new IfStatementParser());
registry.put("loop", new LoopStatementParser());
registry.put("return", new ReturnStatementParser());
// 默认使用表达式语句
// 默认解析器表达式语句
registry.put("", new ExpressionStatementParser());
}
/**
* 根据语句关键字获取对应的语句解析器
*
* @param keyword 语句开头的关键字例如 "if""loop""declare"
* @return 对应的 {@link StatementParser} 实例若无匹配则返回默认解析器
*/
public static StatementParser get(String keyword) {
return registry.getOrDefault(keyword, registry.get(""));
}

View File

@ -7,16 +7,25 @@ import java.util.Map;
import java.util.HashMap;
/**
* 顶层解析器工厂根据首个关键字 module分发对应解析器
* 顶层解析器工厂类用于根据文件开头的关键字 {@code module}分发对应的顶层结构解析器
* 每种顶层结构模块导入等应有一个专门的 {@link TopLevelParser} 实现类
*/
public class TopLevelParserFactory {
private static final Map<String, TopLevelParser> registry = new HashMap<>();
static {
// 注册模块解析器
registry.put("module", new ModuleParser());
// 如需支持 standalone import可在此注册
// importpackage 在此添加注册项
}
/**
* 根据顶层关键字获取对应的解析器
*
* @param keyword 顶层结构的关键字 "module"
* @return 对应的 {@link TopLevelParser} 实例若未注册则返回 null
*/
public static TopLevelParser get(String keyword) {
return registry.get(keyword);
}

View File

@ -11,14 +11,29 @@ import java.util.ArrayList;
import java.util.List;
/**
* 解析 function
* function: NAME NEWLINE
* parameter: (declare name:type | name:type)*
* return_type: TYPE NEWLINE
* body: ... end body
* end function NEWLINE
* 用于解析函数定义块function包括函数名参数列表返回类型函数体等
* 支持的语法结构示例
* <pre>{@code
* function: myFunc
* parameter:
* declare a:int
* b:int
* return_type: int
* body:
* declare x:int = 10
* return x
* end body
* end function
* }</pre>
*/
public class FunctionParser implements TopLevelParser {
/**
* 解析一个函数定义并返回对应的 {@link FunctionNode}
*
* @param ctx 解析上下文
* @return 解析生成的函数节点
*/
@Override
public FunctionNode parse(ParserContext ctx) {
TokenStream ts = ctx.getTokens();
@ -27,7 +42,7 @@ public class FunctionParser implements TopLevelParser {
String name = ts.expectType(TokenType.IDENTIFIER).getLexeme();
ts.expectType(TokenType.NEWLINE);
// 参数列表
// 解析参数列表
ts.expect("parameter");
ts.expect(":");
ts.expectType(TokenType.NEWLINE);
@ -41,7 +56,7 @@ public class FunctionParser implements TopLevelParser {
break;
}
if ("declare".equals(ts.peek().getLexeme())) {
ts.next(); // consume 'declare'
ts.next(); // 支持 declare 前缀
}
String paramName = ts.expectType(TokenType.IDENTIFIER).getLexeme();
ts.expect(":");
@ -50,25 +65,25 @@ public class FunctionParser implements TopLevelParser {
params.add(new ParameterNode(paramName, paramType));
}
// 返回类型
// 解析返回类型
ts.expect("return_type");
ts.expect(":");
String returnType = ts.expectType(TokenType.TYPE).getLexeme();
ts.expectType(TokenType.NEWLINE);
// 函数体解析
// 解析函数体
ts.expect("body");
ts.expect(":");
ts.expectType(TokenType.NEWLINE);
List<StatementNode> body = new ArrayList<>();
parseBody(ctx, body);
// end body
// 解析函数体结束标识
ts.expect("end");
ts.expect("body");
ts.expectType(TokenType.NEWLINE);
// end function
// 解析函数定义结束标识
ts.expect("end");
ts.expect("function");
ts.expectType(TokenType.NEWLINE);
@ -77,7 +92,10 @@ public class FunctionParser implements TopLevelParser {
}
/**
* 辅助方法根据 ParserContext 解析函数体中所有语句直到遇到 end
* 辅助方法从上下文中解析函数体直到遇到 "end"
*
* @param ctx 当前解析上下文
* @param body 存放解析出的语句节点的列表
*/
private void parseBody(ParserContext ctx, List<StatementNode> body) {
TokenStream ts = ctx.getTokens();
@ -90,7 +108,8 @@ public class FunctionParser implements TopLevelParser {
break;
}
String key = ts.peek().getLexeme();
body.add(StatementParserFactory.get(key).parse(ctx));
StatementNode stmt = StatementParserFactory.get(key).parse(ctx);
body.add(stmt);
}
}
}
}

View File

@ -8,27 +8,36 @@ import java.util.ArrayList;
import java.util.List;
/**
* 解析 import 语句import: MOD1, MOD2, ... NEWLINE
* 可以在一行声明多个模块用逗号分隔
* 负责解析 import 语句的解析器
* 支持在一行中导入多个模块格式如
* {@code import: mod1, mod2, mod3}
*/
public class ImportParser {
/**
* 读取一行 import 声明返回多个 ImportNode
* 解析一条 import 声明语句返回对应的 {@link ImportNode} 列表
* 格式必须为{@code import: MODULE[, MODULE]* NEWLINE}
*
* @param ctx 当前的解析上下文
* @return 所有被导入模块的节点列表
*/
public List<ImportNode> parse(ParserContext ctx) {
ctx.getTokens().expect("import");
ctx.getTokens().expect(":");
List<ImportNode> imports = new ArrayList<>();
// 读取第一个模块名
// 继续读取逗号分隔的模块名
// 读取第一个模块名然后继续读取逗号分隔的模块名
do {
String mod = ctx.getTokens()
.expectType(TokenType.IDENTIFIER)
.getLexeme();
imports.add(new ImportNode(mod));
} while (ctx.getTokens().match(","));
// 消费行尾
// 消费行尾换行符
ctx.getTokens().expectType(TokenType.NEWLINE);
return imports;
}
}
}

View File

@ -13,12 +13,24 @@ import java.util.ArrayList;
import java.util.List;
/**
* 解析 module
* module: NAME NEWLINE
* (import | function )*
* end module NEWLINE
* 模块解析器用于解析形如 module 块的顶层结构
* 语法示例
* <pre>{@code
* module: my.module
* import: mod1, mod2
* function: ...
* end module
* }</pre>
* 支持导入语句和多个函数定义允许中间包含空行
*/
public class ModuleParser implements TopLevelParser {
/**
* 解析模块定义块返回 {@link ModuleNode} 表示模块结构
*
* @param ctx 解析上下文
* @return 模块节点包含模块名导入列表和函数列表
*/
@Override
public ModuleNode parse(ParserContext ctx) {
TokenStream ts = ctx.getTokens();
@ -32,17 +44,18 @@ public class ModuleParser implements TopLevelParser {
ImportParser importParser = new ImportParser();
FunctionParser funcParser = new FunctionParser();
// 解析导入和函数支持空行
// 解析模块体包含 import function 语句
while (true) {
// 跳过空行
if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
continue;
}
// 如果遇到 end跳出循环
// 遇到 "end" 表示模块结束
if ("end".equals(ts.peek().getLexeme())) {
break;
}
String lex = ts.peek().getLexeme();
if ("import".equals(lex)) {
imports.addAll(importParser.parse(ctx));
@ -53,9 +66,11 @@ public class ModuleParser implements TopLevelParser {
}
}
// 结束模块块结构
ts.expect("end");
ts.expect("module");
ts.expectType(TokenType.NEWLINE);
return new ModuleNode(name, imports, functions);
}
}

View File

@ -7,24 +7,47 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.PrattExpressionParser;
/**
* 解析 declare 语句 declare name:type (= expr)? NEWLINE
* 解析变量声明语句的解析器
* 支持语法格式
* <pre>{@code
* declare name:type
* declare name:type = expression
* }</pre>
* 每条语句必须以换行NEWLINE结束
*/
public class DeclarationStatementParser implements StatementParser {
/**
* 解析一条 declare 声明语句
*
* @param ctx 当前解析上下文
* @return 构造好的 {@link DeclarationNode} AST 节点
*/
@Override
public DeclarationNode parse(ParserContext ctx) {
ctx.getTokens().expect("declare");
// 获取变量名
String name = ctx.getTokens()
.expectType(TokenType.IDENTIFIER)
.getLexeme();
.expectType(TokenType.IDENTIFIER)
.getLexeme();
ctx.getTokens().expect(":");
// 获取变量类型
String type = ctx.getTokens()
.expectType(TokenType.TYPE)
.getLexeme();
.expectType(TokenType.TYPE)
.getLexeme();
// 可选初始化表达式
ExpressionNode init = null;
if (ctx.getTokens().match("=")) {
init = new PrattExpressionParser().parse(ctx);
}
// 声明语句必须以 NEWLINE 结束
ctx.getTokens().expectType(TokenType.NEWLINE);
return new DeclarationNode(name, type, init);
}
}

View File

@ -10,27 +10,38 @@ import org.jcnc.snow.compiler.parser.context.TokenStream;
import org.jcnc.snow.compiler.parser.expression.PrattExpressionParser;
/**
* 将任意表达式或赋值当作语句
* identifier = expr NEWLINE
* expr NEWLINE
* 表达式语句解析器将赋值表达式或任意表达式作为语句进行解析
* 支持的形式包括
* <pre>
* identifier = expression
* expression
* </pre>
* 两者都必须以换行NEWLINE结尾
*/
public class ExpressionStatementParser implements StatementParser {
/**
* 解析一个表达式语句可能是赋值语句或普通表达式
*
* @param ctx 当前的解析上下文
* @return 表达式语句节点或赋值语句节点
*/
@Override
public StatementNode parse(ParserContext ctx) {
TokenStream ts = ctx.getTokens();
// 先检测赋值语句IDENTIFIER '=' ...
// 检查是否是赋值形式identifier = expr
if (ts.peek().getType() == TokenType.IDENTIFIER
&& ts.peek(1).getLexeme().equals("=")) {
String varName = ts.next().getLexeme(); // consume identifier
ts.expect("="); // consume '='
String varName = ts.next().getLexeme(); // 消费 identifier
ts.expect("="); // 消费 '='
ExpressionNode value = new PrattExpressionParser().parse(ctx);
ts.expectType(TokenType.NEWLINE);
return new AssignmentNode(varName, value);
}
// 否则当作普通表达式语句
// 否则解析为普通表达式语句
ExpressionNode expr = new PrattExpressionParser().parse(ctx);
ts.expectType(TokenType.NEWLINE);
return new ExpressionStatementNode(expr);

View File

@ -11,23 +11,41 @@ import java.util.ArrayList;
import java.util.List;
/**
* 解析 if 语句 if expr then NEWLINE stmts end if NEWLINE
* if 语句解析器支持基本格式
* <pre>{@code
* if condition then
* statements...
* end if
* }</pre>
* 不支持 else 分支仅支持 then 分支
*/
public class IfStatementParser implements StatementParser {
/**
* 解析 if 表达式并构造 {@link IfNode}
*
* @param ctx 当前解析上下文
* @return 表示 if 语句的 AST 节点
*/
@Override
public IfNode parse(ParserContext ctx) {
ctx.getTokens().expect("if");
// 解析条件表达式
var cond = new PrattExpressionParser().parse(ctx);
ctx.getTokens().expect("then");
ctx.getTokens().expectType(TokenType.NEWLINE);
// 解析 then 分支的语句块
List<StatementNode> thenBranch = new ArrayList<>();
while (!ctx.getTokens().peek().getLexeme().equals("end")) {
thenBranch.add(
StatementParserFactory.get(ctx.getTokens().peek().getLexeme()).parse(ctx)
);
String keyword = ctx.getTokens().peek().getLexeme();
StatementNode stmt = StatementParserFactory.get(keyword).parse(ctx);
thenBranch.add(stmt);
}
// 匹配 end if 结束语句
ctx.getTokens().expect("end");
ctx.getTokens().expect("if");
ctx.getTokens().expectType(TokenType.NEWLINE);

View File

@ -14,16 +14,29 @@ import java.util.ArrayList;
import java.util.List;
/**
* 解析 loop 语句
* loop: NEWLINE
* initializer: NEWLINE
* condition: NEWLINE
* update: NEWLINE
* body: NEWLINE
* end body NEWLINE
* end loop NEWLINE
* 用于解析 loop 语句块支持以下结构
* <pre>{@code
* loop:
* initializer:
* declare i:int = 0
* condition:
* i < 10
* update:
* i = i + 1
* body:
* ...语句...
* end body
* end loop
* }</pre>
*/
public class LoopStatementParser implements StatementParser {
/**
* 解析一个完整的 loop 语句块返回 {@link LoopNode}
*
* @param ctx 当前的解析上下文
* @return 表示 loop 结构的 AST 节点
*/
@Override
public LoopNode parse(ParserContext ctx) {
TokenStream ts = ctx.getTokens();
@ -53,7 +66,7 @@ public class LoopStatementParser implements StatementParser {
ts.expectType(TokenType.NEWLINE);
skipNewlines(ts);
// update (专用赋值解析):
// update:
ts.expect("update");
ts.expect(":");
ts.expectType(TokenType.NEWLINE);
@ -90,7 +103,11 @@ public class LoopStatementParser implements StatementParser {
return new LoopNode(initializer, condition, update, body);
}
/** 连续跳过所有空行NEWLINE */
/**
* 跳过连续的 NEWLINE token用于容错和格式整洁
*
* @param ts 当前的 Token
*/
private void skipNewlines(TokenStream ts) {
while (ts.peek().getType() == TokenType.NEWLINE) {
ts.next();

View File

@ -7,19 +7,35 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.PrattExpressionParser;
/**
* 解析 return 语句 return expr? NEWLINE
* 用于解析 return 语句
* 支持有无返回值两种形式
* <pre>{@code
* return
* return expression
* }</pre>
* 语句必须以换行符NEWLINE结束
*/
public class ReturnStatementParser implements StatementParser {
/**
* 解析 return 语句并构建 {@link ReturnNode}
*
* @param ctx 当前解析上下文
* @return 表示 return 语句的 AST 节点
*/
@Override
public ReturnNode parse(ParserContext ctx) {
ctx.getTokens().expect("return");
ExpressionNode expr = null;
// 如果下一个不是换行就解析表达式
// 如果不是换行说明有返回值
if (ctx.getTokens().peek().getType() != TokenType.NEWLINE) {
expr = new PrattExpressionParser().parse(ctx);
}
// expectType 来消费真正的 NEWLINE token
ctx.getTokens().expectType(TokenType.NEWLINE);
return new ReturnNode(expr);
}
}

View File

@ -4,8 +4,16 @@ import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.ast.StatementNode;
/**
* 语句解析器接口
* 语句解析器接口用于将解析上下文中的 Token 转换为 {@link StatementNode}
* 各类语句如声明赋值条件循环返回等均应实现该接口
*/
public interface StatementParser {
/**
* 从给定的解析上下文中解析出一个语句节点
*
* @param ctx 当前的解析上下文
* @return 表示语句的 AST 节点
*/
StatementNode parse(ParserContext ctx);
}