fix: 修复数字字面量解析中的错误处理

- 增加对数字后紧跟未知标识符的错误处理
- 增加对数字后紧跟下划线的错误处理
- 优化数字类型后缀的处理逻辑,防止多字符后缀
This commit is contained in:
Luke 2025-07-16 22:14:40 +08:00
parent 4a26bd50ca
commit c4d9be8403

View File

@ -8,17 +8,17 @@ import org.jcnc.snow.compiler.lexer.token.TokenType;
/** /**
* NumberTokenScanner 基于有限状态机FSM的数字字面量解析器 * NumberTokenScanner 基于有限状态机FSM的数字字面量解析器
* <p> * <p>
* 该扫描器负责将源码中的数字字符串切分为 NUMBER_LITERAL token当前支持: * 该扫描器负责将源码中的数字字符串切分为 NUMBER_LITERAL token当前支持:
* <ol> * <ol>
* <li>十进制整数 042123456</li> * <li>十进制整数 042123456</li>
* <li>十进制小数 3.140.5</li> * <li>十进制小数 3.140.5</li>
* <li>单字符类型后缀 2.0f255B合法集合见 SUFFIX_CHARS</li> * <li>单字符类型后缀 2.0f255B合法集合见 SUFFIX_CHARS</li>
* </ol> * </ol>
* * <p>
* 如果后续需要支持科学计数法下划线分隔符不同进制等只需扩展现有状态机的转移规则 * 如果后续需要支持科学计数法下划线分隔符不同进制等只需扩展现有状态机的转移规则
* *
* <pre> * <pre>
* 状态机简述: * 状态机简述:
* INT_PART --'.'--> DEC_POINT * INT_PART --'.'--> DEC_POINT
* | | * | |
* | v * | v
@ -27,21 +27,21 @@ import org.jcnc.snow.compiler.lexer.token.TokenType;
* v * v
* DEC_POINT --digit--> FRAC_PART * DEC_POINT --digit--> FRAC_PART
* </pre> * </pre>
* 状态说明: * 状态说明:
* <ul> * <ul>
* <li>INT_PART : 读取整数部分遇到 '.' 进入 DEC_POINT否则结束</li> * <li>INT_PART : 读取整数部分遇到 '.' 进入 DEC_POINT否则结束</li>
* <li>DEC_POINT : 已读到小数点必须下一个字符是数字否则报错</li> * <li>DEC_POINT : 已读到小数点必须下一个字符是数字否则报错</li>
* <li>FRAC_PART : 读取小数部分遇非法字符则结束主体</li> * <li>FRAC_PART : 读取小数部分遇非法字符则结束主体</li>
* <li>END : 主体扫描结束进入后缀/尾随字符判定</li> * <li>END : 主体扫描结束进入后缀/尾随字符判定</li>
* </ul> * </ul>
* * <p>
* 错误处理策略: * 错误处理策略:
* <ol> * <ol>
* <li>数字后跟未知字母 42X 抛出 LexicalException</li> * <li>数字后跟未知字母 42X 抛出 LexicalException</li>
* <li>数字与合法后缀间有空白 3 L 抛出 LexicalException</li> * <li>数字与合法后缀间有空白 3 L 抛出 LexicalException</li>
* <li>小数点后缺失数字 1. 抛出 LexicalException</li> * <li>小数点后缺失数字 1. 抛出 LexicalException</li>
* </ol> * </ol>
* * <p>
* 支持的单字符类型后缀包括: b, s, l, f, d 及其大写形式若需支持多字符后缀可将该集合扩展为 Set<String> * 支持的单字符类型后缀包括: b, s, l, f, d 及其大写形式若需支持多字符后缀可将该集合扩展为 Set<String>
*/ */
public class NumberTokenScanner extends AbstractTokenScanner { public class NumberTokenScanner extends AbstractTokenScanner {
@ -125,13 +125,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. 未知紧邻字母后缀 —— 报错 */ /* 后缀只能出现一次:再次出现字母/数字/点即视为非法 */
else if (Character.isLetter(next)) { if (!ctx.isAtEnd()) {
throw new LexicalException("未知的数字类型后缀 '" + next + "'", line, col); char peekAfterSuffix = ctx.peek();
if (Character.isLetter(peekAfterSuffix)
|| Character.isDigit(peekAfterSuffix)
|| peekAfterSuffix == '.') {
throw new LexicalException(
"数字类型后缀只能是单字符,非法续接 '" + peekAfterSuffix + "'",
line, col);
}
}
/* 2-B. **非法字母**(既不是后缀,也没有空白隔开) */
} else if (Character.isLetter(next)) {
throw new LexicalException(
"数字后不能紧跟未知标识符 '" + next + "'", line, col);
/* 2-C. **非法下划线** */
} else if (next == '_') {
throw new LexicalException(
"数字后不能紧跟下划线 '_'", line, col);
} }
/* 其余情况交由外层扫描器处理(包括空白及其它符号) */ /* 其余情况交由外层扫描器处理(包括空白及其它符号) */
} }
@ -140,15 +157,25 @@ public class NumberTokenScanner extends AbstractTokenScanner {
return new Token(TokenType.NUMBER_LITERAL, literal.toString(), line, col); return new Token(TokenType.NUMBER_LITERAL, literal.toString(), line, col);
} }
/** FSM 内部状态定义 */ /**
* FSM 内部状态定义
*/
private enum State { private enum State {
/** 整数部分 */ /**
* 整数部分
*/
INT_PART, INT_PART,
/** 已读到小数点,但还未读到第一位小数数字 */ /**
* 已读到小数点但还未读到第一位小数数字
*/
DEC_POINT, DEC_POINT,
/** 小数部分 */ /**
* 小数部分
*/
FRAC_PART, FRAC_PART,
/** 主体结束,准备处理后缀或交还控制权 */ /**
* 主体结束准备处理后缀或交还控制权
*/
END END
} }
} }