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- * 同时会跳过连续空行。 - *
- * - * @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