feat: 增强数字字面量的语义分析和错误提示

- 添加类型推断和范围校验逻辑,支持 byte、short、int、long、float 和 double 类型
- 实现溢出和格式错误的统一处理,提供详细的错误提示
- 优化代码结构,增加私有方法以提高可读性和可维护性
This commit is contained in:
Luke 2025-07-30 12:03:41 +08:00
parent 3dfc8b63c8
commit fdeaa36366

View File

@ -5,50 +5,154 @@ import org.jcnc.snow.compiler.parser.ast.NumberLiteralNode;
import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer; import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer;
import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.Context;
import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
import org.jcnc.snow.compiler.semantic.error.SemanticError;
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.BuiltinType;
import org.jcnc.snow.compiler.semantic.type.Type; import org.jcnc.snow.compiler.semantic.type.Type;
import static org.jcnc.snow.compiler.semantic.type.BuiltinType.*;
/** /**
* {@code NumberLiteralAnalyzer} 用于对数字字面量表达式进行语义分析并推断其精确类型 * {@code NumberLiteralAnalyzer} 用于对数字字面量表达式进行语义分析并推断其精确类型并支持类型范围校验与多错误收集
* <p> * <p>
* 类型判定逻辑如下: * 类型推断规则如下
* <ol> * <ol>
* <li>首先检查字面量末尾是否带有类型后缀不区分大小写: * <li>若字面量以类型后缀b/s/l/f/d大小写均可结尾则按后缀直接推断目标类型</li>
* <ul> * <li>若无后缀且文本包含小数点或科学计数法'.' 'e/E'则推断为 double 类型</li>
* <li>{@code b}: byte 类型{@link BuiltinType#BYTE}</li> * <li>否则推断为 int 类型</li>
* <li>{@code s}: short 类型{@link BuiltinType#SHORT}</li>
* <li>{@code l}: long 类型{@link BuiltinType#LONG}</li>
* <li>{@code f}: float 类型{@link BuiltinType#FLOAT}</li>
* <li>{@code d}: double 类型{@link BuiltinType#DOUBLE}</li>
* </ul>
* </li>
* <li>若无后缀:
* <ul>
* <li>只要文本中包含 {@code '.'} {@code e/E}即判为 double 类型</li>
* <li>否则默认判为 int 类型</li>
* </ul>
* </li>
* </ol> * </ol>
* 本分析器不处理溢出非法格式等诊断仅做类型推断 * <p>
* 校验规则如下
* <ul>
* <li>按推断类型解析字符串若超出可表示范围或格式非法则向语义上下文添加 {@link SemanticError}不抛异常支持多错误收集和 IDE 友好提示</li>
* <li>所有建议信息风格统一指引用户用更大类型的后缀或类型声明最大类型提示为 double</li>
* </ul>
*/ */
public class NumberLiteralAnalyzer implements ExpressionAnalyzer<NumberLiteralNode> { public class NumberLiteralAnalyzer implements ExpressionAnalyzer<NumberLiteralNode> {
/** /**
* 对数字字面量进行语义分析推断其类型 * 根据字面量后缀和文本内容推断类型
* <p>
* 分析流程:
* <ol>
* <li>若字面量以后缀结尾直接按后缀映射类型</li>
* <li>否则若含有小数点或科学计数法标记则为 double否则为 int</li>
* </ol>
* *
* @param ctx 当前语义分析上下文可用于记录诊断信息等当前未使用 * @param hasSuffix 是否有类型后缀
* @param suffix 类型后缀字符
* @param digits 去除后缀的纯数字字符串
* @return 推断出的类型
*/
private static Type inferType(boolean hasSuffix, char suffix, String digits) {
if (hasSuffix) {
return switch (suffix) {
case 'b' -> BYTE;
case 's' -> SHORT;
case 'l' -> BuiltinType.LONG;
case 'f' -> BuiltinType.FLOAT;
case 'd' -> BuiltinType.DOUBLE;
default -> INT;
};
}
if (digits.indexOf('.') >= 0 || digits.indexOf('e') >= 0 || digits.indexOf('E') >= 0) {
return BuiltinType.DOUBLE;
}
return INT;
}
/**
* 检查字面量数值是否在推断类型范围内超出范围则添加语义错误
*
* @param ctx 语义分析上下文
* @param node 当前字面量节点
* @param raw 字面量原始字符串含后缀
* @param inferred 推断类型
* @param digits 去除后缀的纯数字部分
*/
private static void validateRange(Context ctx,
NumberLiteralNode node,
String raw,
Type inferred,
String digits) {
try {
if (inferred == BYTE) {
if (looksLikeFloat(digits)) throw new NumberFormatException(digits);
Byte.parseByte(digits);
} else if (inferred == SHORT) {
if (looksLikeFloat(digits)) throw new NumberFormatException(digits);
Short.parseShort(digits);
} else if (inferred == INT) {
if (looksLikeFloat(digits)) throw new NumberFormatException(digits);
Integer.parseInt(digits);
} else if (inferred == BuiltinType.LONG) {
if (looksLikeFloat(digits)) throw new NumberFormatException(digits);
Long.parseLong(digits);
} else if (inferred == BuiltinType.FLOAT) {
float v = Float.parseFloat(digits);
if (Float.isInfinite(v)) throw new NumberFormatException(digits);
} else if (inferred == BuiltinType.DOUBLE) {
double v = Double.parseDouble(digits);
if (Double.isInfinite(v)) throw new NumberFormatException(digits);
}
} catch (NumberFormatException ex) {
String typeName = switch (inferred) {
case BYTE -> "byte";
case SHORT -> "short";
case INT -> "int";
case LONG -> "long";
case FLOAT -> "float";
case DOUBLE -> "double";
default -> inferred.toString();
};
String msg = getString(raw, digits, typeName);
ctx.getErrors().add(new SemanticError(node, msg));
ctx.log("错误: " + msg);
}
}
/**
* 根据类型名生成风格统一的溢出建议字符串
*
* @param raw 字面量原始字符串
* @param digits 去除后缀的数字部分
* @param typeName 推断类型的名称
* @return 完整的错误描述
*/
private static String getString(String raw, String digits, String typeName) {
String suggestion = switch (typeName) {
case "int" ->
"如需更大范围,请在数字末尾添加大写字母 'L'(如 " + digits + "L或将变量类型声明为 long。";
case "long" ->
"long 已为整数的最大类型,如需更大范围,请将变量类型声明为 double注意精度";
case "float" ->
"如需更大范围,请将变量类型声明为 double。";
case "byte" ->
"如需更大范围,请在数字末尾添加大写字母 'S'(如 " + digits + "S或将变量类型声明为 short、int 或 long。";
case "short" ->
"如需更大范围,请在数字末尾添加大写字母 'L'(如 " + digits + "L或将变量类型声明为 int 或 long。";
case "double" ->
"double 已为数值类型的最大范围,请缩小数值。";
default ->
"请调整字面量类型或范围。";
};
return "数值字面量越界: \"" + raw + "\" 超出 " + typeName + " 可表示范围。 " + suggestion;
}
/**
* 判断字符串是否为浮点数格式包含小数点或科学计数法
*
* @param s 数字部分字符串
* @return 是否为浮点格式
*/
private static boolean looksLikeFloat(String s) {
return s.indexOf('.') >= 0 || s.indexOf('e') >= 0 || s.indexOf('E') >= 0;
}
/**
* 对数字字面量进行语义分析推断类型并进行溢出与格式校验
*
* @param ctx 当前语义分析上下文可用于错误收集等
* @param mi 当前模块信息未使用 * @param mi 当前模块信息未使用
* @param fn 当前函数节点未使用 * @param fn 当前函数节点未使用
* @param locals 当前作用域的符号表未使用 * @param locals 当前作用域的符号表未使用
* @param expr 数字字面量表达式节点 * @param expr 数字字面量表达式节点
* @return 对应的 {@link BuiltinType} INTDOUBLE * @return 推断出的类型
*/ */
@Override @Override
public Type analyze(Context ctx, public Type analyze(Context ctx,
@ -60,38 +164,24 @@ public class NumberLiteralAnalyzer implements ExpressionAnalyzer<NumberLiteralNo
// 获取字面量原始文本 "123", "3.14", "2f" // 获取字面量原始文本 "123", "3.14", "2f"
String raw = expr.value(); String raw = expr.value();
if (raw == null || raw.isEmpty()) { if (raw == null || raw.isEmpty()) {
// 理论上不应为空兜底返回 int 类型 return INT;
return BuiltinType.INT;
} }
// 获取最后一个字符判断是否为类型后缀b/s/l/f/d忽略大小写 // 判断并去除后缀
char lastChar = raw.charAt(raw.length() - 1); char lastChar = raw.charAt(raw.length() - 1);
char suffix = Character.toLowerCase(lastChar); char suffix = Character.toLowerCase(lastChar);
boolean hasSuffix = switch (suffix) { boolean hasSuffix = switch (suffix) {
case 'b', 's', 'l', 'f', 'd' -> true; case 'b', 's', 'l', 'f', 'd' -> true;
default -> false; default -> false;
}; };
// 若有后缀 digits 为去除后缀的数字部分否则为原文本
String digits = hasSuffix ? raw.substring(0, raw.length() - 1) : raw; String digits = hasSuffix ? raw.substring(0, raw.length() - 1) : raw;
// 1. 若有后缀直接返回对应类型 // 推断类型
if (hasSuffix) { Type inferred = inferType(hasSuffix, suffix, digits);
return switch (suffix) {
case 'b' -> BuiltinType.BYTE;
case 's' -> BuiltinType.SHORT;
case 'l' -> BuiltinType.LONG;
case 'f' -> BuiltinType.FLOAT;
case 'd' -> BuiltinType.DOUBLE;
default -> BuiltinType.INT; // 理论上不会到这里
};
}
// 2. 无后缀根据文本是否含小数点或科学计数法e/E判断类型 // 做范围校验发现溢出等问题直接加到 ctx 错误列表
if (digits.indexOf('.') >= 0 || digits.indexOf('e') >= 0 || digits.indexOf('E') >= 0) { validateRange(ctx, expr, raw, inferred, digits);
return BuiltinType.DOUBLE; // 有小数/科学计数默认 double 类型
}
return BuiltinType.INT; // 否则为纯整数默认 int 类型 return inferred;
} }
} }