From e33f6b0ce292bdca99086e453ec20b968cdd9a07 Mon Sep 17 00:00:00 2001
From: Luke
Date: Sat, 5 Jul 2025 14:20:43 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=95=B0=E5=AD=97?=
=?UTF-8?q?=E5=90=8E=E7=A9=BA=E6=A0=BC=E5=90=8E=E6=8E=A5=E4=B8=8A=E9=9D=9E?=
=?UTF-8?q?=E6=B3=95=E5=90=8E=E7=BC=80=E8=BF=9B=E5=85=A5=E6=AD=BB=E5=BE=AA?=
=?UTF-8?q?=E7=8E=AF=E7=9A=84=E9=94=99=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../lexer/scanners/NumberTokenScanner.java | 35 ++++++-----
.../compiler/parser/core/ParserEngine.java | 61 +++++--------------
.../parser/factory/TopLevelParserFactory.java | 24 ++++++--
3 files changed, 54 insertions(+), 66 deletions(-)
diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/NumberTokenScanner.java b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/NumberTokenScanner.java
index 1a9b8bb..eef90ae 100644
--- a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/NumberTokenScanner.java
+++ b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/NumberTokenScanner.java
@@ -128,33 +128,30 @@ public class NumberTokenScanner extends AbstractTokenScanner {
if (!ctx.isAtEnd()) {
char next = ctx.peek();
- // 2‑A. 合法单字符后缀
+ // 2-A. 合法单字符后缀(紧邻,不允许空格)
if (SUFFIX_CHARS.indexOf(next) >= 0) {
literal.append(ctx.advance());
}
- // 2‑B. 紧跟未知字母(如 42X)
+ // 未知单字符后缀 —— 直接报错
else if (Character.isLetter(next)) {
throw new LexicalException("未知的数字类型后缀 '" + next + "'", line, col);
}
- // 2‑C. 数字后出现空白 + 类型后缀(如 3 f) —— 不允许
+ // “数字 + 空格 + 字母” —— 一律非法
else if (Character.isWhitespace(next) && next != '\n') {
- // 允许数字后与普通标识符/关键字间存在空白;
- // 仅当空白后的首个非空字符是合法的类型后缀时才报错。
int off = 1;
char look;
- // 跳过任意空白(不含换行)
+ // 跳过空白(不含换行)
while (true) {
look = ctx.peekAhead(off);
- if (look == '\n' || look == '\0') break; // 行尾或 EOF
+ if (look == '\n' || look == '\0') break;
if (!Character.isWhitespace(look)) break;
off++;
}
- // 如果紧跟类型后缀字符,中间存在空白则视为非法
- if (SUFFIX_CHARS.indexOf(look) >= 0) {
- throw new LexicalException("数字字面量与类型后缀之间不允许有空白符", line, col);
+ if (Character.isLetter(look)) {
+ throw new LexicalException("数字字面量后不允许出现空格再跟标识符/后缀", line, col);
}
}
- // 其他字符(分号、运算符、括号等)留给外层扫描流程处理
+ // 其他符号由外层扫描器处理
}
// 3. 生成并返回 Token
@@ -165,13 +162,21 @@ public class NumberTokenScanner extends AbstractTokenScanner {
* FSM 内部状态。
*/
private enum State {
- /** 整数部分(小数点左侧) */
+ /**
+ * 整数部分(小数点左侧)
+ */
INT_PART,
- /** 已读到小数点,但还未读到第一位小数数字 */
+ /**
+ * 已读到小数点,但还未读到第一位小数数字
+ */
DEC_POINT,
- /** 小数部分(小数点右侧) */
+ /**
+ * 小数部分(小数点右侧)
+ */
FRAC_PART,
- /** 主体结束,准备处理后缀或交还控制权 */
+ /**
+ * 主体结束,准备处理后缀或交还控制权
+ */
END
}
}
diff --git a/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java b/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java
index c5bfb36..f278b41 100644
--- a/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java
+++ b/src/main/java/org/jcnc/snow/compiler/parser/core/ParserEngine.java
@@ -14,61 +14,34 @@ import java.util.StringJoiner;
/**
* 语法解析引擎(ParserEngine)。
- *
- * 负责驱动 Snow 源码的顶层语法结构解析,将源码 TokenStream
- * 递交给各类 TopLevelParser,并收集语法树节点与异常。
- * 支持容错解析,能够批量报告所有语法错误,并提供同步恢复功能。
- *
- *
- *
- * 典型用法:
- *
- * ParserEngine engine = new ParserEngine(context);
- * List<Node> ast = engine.parse();
- *
- *
- *
- * @param ctx 解析器上下文,负责持有 TokenStream 及所有全局状态。
+ * 驱动顶层解析,并在捕获异常后通过同步机制恢复,防止死循环。
*/
public record ParserEngine(ParserContext ctx) {
- /**
- * 解析输入 TokenStream,生成语法树节点列表。
- *
- *
- * 调用各类顶级语句解析器(如 module, func, import),
- * 遇到错误时会自动跳过到下一行或已知结构关键字,继续后续分析,
- * 最终汇总所有错误。如果解析出现错误,将以
- * {@link UnexpectedToken} 抛出所有语法错误信息。
- *
- *
- * @return AST 节点列表,每个节点对应一个顶层语法结构
- * @throws UnexpectedToken 如果解析期间发现语法错误
- */
+ /** 解析整份 TokenStream,返回顶层 AST 节点列表。 */
public List parse() {
List nodes = new ArrayList<>();
- List errs = new ArrayList<>();
- TokenStream ts = ctx.getTokens();
+ List errs = new ArrayList<>();
+ TokenStream ts = ctx.getTokens();
- // 主循环:直到全部 token 处理完毕
+ // 主循环至 EOF
while (ts.isAtEnd()) {
- // 跳过所有空行
+ // 跳过空行
if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
continue;
}
TopLevelParser parser = TopLevelParserFactory.get(ts.peek().getLexeme());
-
try {
nodes.add(parser.parse(ctx));
} catch (Exception ex) {
errs.add(ex.getMessage());
- synchronize(ts); // 错误恢复:同步到下一个语句
+ synchronize(ts); // 出错后同步恢复
}
}
- // 批量报告所有解析错误
+ // 聚合并抛出全部语法错误
if (!errs.isEmpty()) {
StringJoiner sj = new StringJoiner("\n - ", "", "");
errs.forEach(sj::add);
@@ -79,27 +52,21 @@ public record ParserEngine(ParserContext ctx) {
}
/**
- * 错误同步机制:跳过当前 TokenStream,直到遇到下一行
- * 或下一个可识别的顶级结构关键字,以保证后续解析不会被卡住。
- *
- * 同时会跳过连续空行。
- *
- *
- * @param ts 当前 TokenStream
+ * 同步:跳过当前行或直到遇到 **显式注册** 的顶层关键字。
+ * 这样可避免因默认脚本解析器导致指针停滞而进入死循环。
*/
private void synchronize(TokenStream ts) {
- // 跳到下一行或下一个顶层结构关键字
while (ts.isAtEnd()) {
if (ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
break;
}
- if (TopLevelParserFactory.get(ts.peek().getLexeme()) != null) {
- break;
+ if (TopLevelParserFactory.isRegistered(ts.peek().getLexeme())) {
+ break; // 仅在已注册关键字处停下
}
- ts.next();
+ ts.next(); // 继续丢弃 token
}
- // 吃掉后续所有空行
+ // 清掉后续连续空行
while (ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) {
ts.next();
}
diff --git a/src/main/java/org/jcnc/snow/compiler/parser/factory/TopLevelParserFactory.java b/src/main/java/org/jcnc/snow/compiler/parser/factory/TopLevelParserFactory.java
index 7fa779c..82c486f 100644
--- a/src/main/java/org/jcnc/snow/compiler/parser/factory/TopLevelParserFactory.java
+++ b/src/main/java/org/jcnc/snow/compiler/parser/factory/TopLevelParserFactory.java
@@ -8,22 +8,38 @@ import org.jcnc.snow.compiler.parser.top.ScriptTopLevelParser;
import java.util.Map;
import java.util.HashMap;
+/**
+ * {@code TopLevelParserFactory} 用于根据源码中顶层关键字取得对应的解析器。
+ *
+ * 若关键字未注册,则回退到脚本模式解析器 {@link ScriptTopLevelParser}。
+ */
public class TopLevelParserFactory {
+ /** 关键字 → 解析器注册表 */
private static final Map registry = new HashMap<>();
- private static final TopLevelParser DEFAULT = new ScriptTopLevelParser(); // ← 默认解析器
+
+ /** 缺省解析器:脚本模式(单条语句可执行) */
+ private static final TopLevelParser DEFAULT = new ScriptTopLevelParser();
static {
- // 顶层结构解析器
+ // 在此注册所有受支持的顶层结构关键字
registry.put("module", new ModuleParser());
registry.put("function", new FunctionParser());
- // 也可按需继续注册其它关键字
+ // 若未来新增顶层结构,可继续在此处注册
}
/**
- * 根据关键字获取解析器;若未注册,回退到脚本语句解析。
+ * 依据关键字返回解析器;若未注册则返回脚本解析器。
*/
public static TopLevelParser get(String keyword) {
return registry.getOrDefault(keyword, DEFAULT);
}
+
+ /**
+ * 判断某关键字是否已显式注册为顶层结构,
+ * 供同步恢复逻辑使用,避免死循环。
+ */
+ public static boolean isRegistered(String keyword) {
+ return registry.containsKey(keyword);
+ }
}