fix: 修复数字后空格后接上非法后缀进入死循环的错误
This commit is contained in:
parent
169523bc33
commit
e33f6b0ce2
@ -128,33 +128,30 @@ public class NumberTokenScanner extends AbstractTokenScanner {
|
|||||||
if (!ctx.isAtEnd()) {
|
if (!ctx.isAtEnd()) {
|
||||||
char next = ctx.peek();
|
char next = ctx.peek();
|
||||||
|
|
||||||
// 2‑A. 合法单字符后缀
|
// 2-A. 合法单字符后缀(紧邻,不允许空格)
|
||||||
if (SUFFIX_CHARS.indexOf(next) >= 0) {
|
if (SUFFIX_CHARS.indexOf(next) >= 0) {
|
||||||
literal.append(ctx.advance());
|
literal.append(ctx.advance());
|
||||||
}
|
}
|
||||||
// 2‑B. 紧跟未知字母(如 42X)
|
// 未知单字符后缀 —— 直接报错
|
||||||
else if (Character.isLetter(next)) {
|
else if (Character.isLetter(next)) {
|
||||||
throw new LexicalException("未知的数字类型后缀 '" + next + "'", line, col);
|
throw new LexicalException("未知的数字类型后缀 '" + next + "'", line, col);
|
||||||
}
|
}
|
||||||
// 2‑C. 数字后出现空白 + 类型后缀(如 3 f) —— 不允许
|
// “数字 + 空格 + 字母” —— 一律非法
|
||||||
else if (Character.isWhitespace(next) && next != '\n') {
|
else if (Character.isWhitespace(next) && next != '\n') {
|
||||||
// 允许数字后与普通标识符/关键字间存在空白;
|
|
||||||
// 仅当空白后的首个非空字符是合法的类型后缀时才报错。
|
|
||||||
int off = 1;
|
int off = 1;
|
||||||
char look;
|
char look;
|
||||||
// 跳过任意空白(不含换行)
|
// 跳过空白(不含换行)
|
||||||
while (true) {
|
while (true) {
|
||||||
look = ctx.peekAhead(off);
|
look = ctx.peekAhead(off);
|
||||||
if (look == '\n' || look == '\0') break; // 行尾或 EOF
|
if (look == '\n' || look == '\0') break;
|
||||||
if (!Character.isWhitespace(look)) break;
|
if (!Character.isWhitespace(look)) break;
|
||||||
off++;
|
off++;
|
||||||
}
|
}
|
||||||
// 如果紧跟类型后缀字符,中间存在空白则视为非法
|
if (Character.isLetter(look)) {
|
||||||
if (SUFFIX_CHARS.indexOf(look) >= 0) {
|
throw new LexicalException("数字字面量后不允许出现空格再跟标识符/后缀", line, col);
|
||||||
throw new LexicalException("数字字面量与类型后缀之间不允许有空白符", line, col);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 其他字符(分号、运算符、括号等)留给外层扫描流程处理
|
// 其他符号由外层扫描器处理
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 生成并返回 Token
|
// 3. 生成并返回 Token
|
||||||
@ -165,13 +162,21 @@ public class NumberTokenScanner extends AbstractTokenScanner {
|
|||||||
* FSM 内部状态。
|
* FSM 内部状态。
|
||||||
*/
|
*/
|
||||||
private enum State {
|
private enum State {
|
||||||
/** 整数部分(小数点左侧) */
|
/**
|
||||||
|
* 整数部分(小数点左侧)
|
||||||
|
*/
|
||||||
INT_PART,
|
INT_PART,
|
||||||
/** 已读到小数点,但还未读到第一位小数数字 */
|
/**
|
||||||
|
* 已读到小数点,但还未读到第一位小数数字
|
||||||
|
*/
|
||||||
DEC_POINT,
|
DEC_POINT,
|
||||||
/** 小数部分(小数点右侧) */
|
/**
|
||||||
|
* 小数部分(小数点右侧)
|
||||||
|
*/
|
||||||
FRAC_PART,
|
FRAC_PART,
|
||||||
/** 主体结束,准备处理后缀或交还控制权 */
|
/**
|
||||||
|
* 主体结束,准备处理后缀或交还控制权
|
||||||
|
*/
|
||||||
END
|
END
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,61 +14,34 @@ import java.util.StringJoiner;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 语法解析引擎(ParserEngine)。
|
* 语法解析引擎(ParserEngine)。
|
||||||
* <p>
|
* <p>驱动顶层解析,并在捕获异常后通过同步机制恢复,防止死循环。</p>
|
||||||
* 负责驱动 Snow 源码的顶层语法结构解析,将源码 TokenStream
|
|
||||||
* 递交给各类 TopLevelParser,并收集语法树节点与异常。
|
|
||||||
* 支持容错解析,能够批量报告所有语法错误,并提供同步恢复功能。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 典型用法:
|
|
||||||
* <pre>
|
|
||||||
* ParserEngine engine = new ParserEngine(context);
|
|
||||||
* List<Node> ast = engine.parse();
|
|
||||||
* </pre>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param ctx 解析器上下文,负责持有 TokenStream 及所有全局状态。
|
|
||||||
*/
|
*/
|
||||||
public record ParserEngine(ParserContext ctx) {
|
public record ParserEngine(ParserContext ctx) {
|
||||||
|
|
||||||
/**
|
/** 解析整份 TokenStream,返回顶层 AST 节点列表。 */
|
||||||
* 解析输入 TokenStream,生成语法树节点列表。
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 调用各类顶级语句解析器(如 module, func, import),
|
|
||||||
* 遇到错误时会自动跳过到下一行或已知结构关键字,继续后续分析,
|
|
||||||
* 最终汇总所有错误。如果解析出现错误,将以
|
|
||||||
* {@link UnexpectedToken} 抛出所有语法错误信息。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @return AST 节点列表,每个节点对应一个顶层语法结构
|
|
||||||
* @throws UnexpectedToken 如果解析期间发现语法错误
|
|
||||||
*/
|
|
||||||
public List<Node> parse() {
|
public List<Node> parse() {
|
||||||
List<Node> nodes = new ArrayList<>();
|
List<Node> nodes = new ArrayList<>();
|
||||||
List<String> errs = new ArrayList<>();
|
List<String> errs = new ArrayList<>();
|
||||||
TokenStream ts = ctx.getTokens();
|
TokenStream ts = ctx.getTokens();
|
||||||
|
|
||||||
// 主循环:直到全部 token 处理完毕
|
// 主循环至 EOF
|
||||||
while (ts.isAtEnd()) {
|
while (ts.isAtEnd()) {
|
||||||
// 跳过所有空行
|
// 跳过空行
|
||||||
if (ts.peek().getType() == TokenType.NEWLINE) {
|
if (ts.peek().getType() == TokenType.NEWLINE) {
|
||||||
ts.next();
|
ts.next();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
TopLevelParser parser = TopLevelParserFactory.get(ts.peek().getLexeme());
|
TopLevelParser parser = TopLevelParserFactory.get(ts.peek().getLexeme());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
nodes.add(parser.parse(ctx));
|
nodes.add(parser.parse(ctx));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
errs.add(ex.getMessage());
|
errs.add(ex.getMessage());
|
||||||
synchronize(ts); // 错误恢复:同步到下一个语句
|
synchronize(ts); // 出错后同步恢复
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量报告所有解析错误
|
// 聚合并抛出全部语法错误
|
||||||
if (!errs.isEmpty()) {
|
if (!errs.isEmpty()) {
|
||||||
StringJoiner sj = new StringJoiner("\n - ", "", "");
|
StringJoiner sj = new StringJoiner("\n - ", "", "");
|
||||||
errs.forEach(sj::add);
|
errs.forEach(sj::add);
|
||||||
@ -79,27 +52,21 @@ public record ParserEngine(ParserContext ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误同步机制:跳过当前 TokenStream,直到遇到下一行
|
* 同步:跳过当前行或直到遇到 **显式注册** 的顶层关键字。
|
||||||
* 或下一个可识别的顶级结构关键字,以保证后续解析不会被卡住。
|
* 这样可避免因默认脚本解析器导致指针停滞而进入死循环。
|
||||||
* <p>
|
|
||||||
* 同时会跳过连续空行。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param ts 当前 TokenStream
|
|
||||||
*/
|
*/
|
||||||
private void synchronize(TokenStream ts) {
|
private void synchronize(TokenStream ts) {
|
||||||
// 跳到下一行或下一个顶层结构关键字
|
|
||||||
while (ts.isAtEnd()) {
|
while (ts.isAtEnd()) {
|
||||||
if (ts.peek().getType() == TokenType.NEWLINE) {
|
if (ts.peek().getType() == TokenType.NEWLINE) {
|
||||||
ts.next();
|
ts.next();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (TopLevelParserFactory.get(ts.peek().getLexeme()) != null) {
|
if (TopLevelParserFactory.isRegistered(ts.peek().getLexeme())) {
|
||||||
break;
|
break; // 仅在已注册关键字处停下
|
||||||
}
|
}
|
||||||
ts.next();
|
ts.next(); // 继续丢弃 token
|
||||||
}
|
}
|
||||||
// 吃掉后续所有空行
|
// 清掉后续连续空行
|
||||||
while (ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) {
|
while (ts.isAtEnd() && ts.peek().getType() == TokenType.NEWLINE) {
|
||||||
ts.next();
|
ts.next();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,22 +8,38 @@ import org.jcnc.snow.compiler.parser.top.ScriptTopLevelParser;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code TopLevelParserFactory} 用于根据源码中顶层关键字取得对应的解析器。
|
||||||
|
* <p>
|
||||||
|
* 若关键字未注册,则回退到脚本模式解析器 {@link ScriptTopLevelParser}。
|
||||||
|
*/
|
||||||
public class TopLevelParserFactory {
|
public class TopLevelParserFactory {
|
||||||
|
|
||||||
|
/** 关键字 → 解析器注册表 */
|
||||||
private static final Map<String, TopLevelParser> registry = new HashMap<>();
|
private static final Map<String, TopLevelParser> registry = new HashMap<>();
|
||||||
private static final TopLevelParser DEFAULT = new ScriptTopLevelParser(); // ← 默认解析器
|
|
||||||
|
/** 缺省解析器:脚本模式(单条语句可执行) */
|
||||||
|
private static final TopLevelParser DEFAULT = new ScriptTopLevelParser();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// 顶层结构解析器
|
// 在此注册所有受支持的顶层结构关键字
|
||||||
registry.put("module", new ModuleParser());
|
registry.put("module", new ModuleParser());
|
||||||
registry.put("function", new FunctionParser());
|
registry.put("function", new FunctionParser());
|
||||||
// 也可按需继续注册其它关键字
|
// 若未来新增顶层结构,可继续在此处注册
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据关键字获取解析器;若未注册,回退到脚本语句解析。
|
* 依据关键字返回解析器;若未注册则返回脚本解析器。
|
||||||
*/
|
*/
|
||||||
public static TopLevelParser get(String keyword) {
|
public static TopLevelParser get(String keyword) {
|
||||||
return registry.getOrDefault(keyword, DEFAULT);
|
return registry.getOrDefault(keyword, DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断某关键字是否已显式注册为顶层结构,
|
||||||
|
* 供同步恢复逻辑使用,避免死循环。
|
||||||
|
*/
|
||||||
|
public static boolean isRegistered(String keyword) {
|
||||||
|
return registry.containsKey(keyword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user