feat: 增强数字字面量的语义分析和错误提示
- 添加类型推断和范围校验逻辑,支持 byte、short、int、long、float 和 double 类型 - 实现溢出和格式错误的统一处理,提供详细的错误提示 - 优化代码结构,增加私有方法以提高可读性和可维护性
This commit is contained in:
parent
3dfc8b63c8
commit
fdeaa36366
@ -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},如 INT、DOUBLE 等
|
* @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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user