diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/NumberLiteralAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/NumberLiteralAnalyzer.java index c92b504..7f6625f 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/NumberLiteralAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/NumberLiteralAnalyzer.java @@ -13,19 +13,42 @@ import org.jcnc.snow.compiler.semantic.type.Type; import static org.jcnc.snow.compiler.semantic.type.BuiltinType.*; /** - * {@code NumberLiteralAnalyzer} 用于对数字字面量表达式进行语义分析并推断其精确类型,并支持类型范围校验与多错误收集。 - *

- * 类型推断规则如下: - *

    - *
  1. 若字面量以类型后缀(b/s/l/f/d,大小写均可)结尾,则按后缀直接推断目标类型。
  2. - *
  3. 若无后缀,且文本包含小数点或科学计数法('.' 或 'e/E'),则推断为 double 类型。
  4. - *
  5. 否则推断为 int 类型。
  6. - *
- *

- * 校验规则如下: + * {@code NumberLiteralAnalyzer} + * + *

职责: *

+ * + *

类型推断规则:

+ *
    + *
  1. 若字面量以类型后缀(b/s/l/f,大小写均可)结尾,则按后缀直接推断目标类型:
  2. + * + *
  3. 若无后缀,且文本包含小数点或科学计数法('.' 或 'e/E'),则推断为 double(浮点默认 double,不支持 d/D 后缀);
  4. + *
  5. 否则推断为 int。
  6. + *
+ * + *

智能错误提示策略:

+ * */ public class NumberLiteralAnalyzer implements ExpressionAnalyzer { @@ -33,43 +56,43 @@ public class NumberLiteralAnalyzer implements ExpressionAnalyzer BYTE; case 's' -> SHORT; case 'l' -> BuiltinType.LONG; case 'f' -> BuiltinType.FLOAT; - case 'd' -> BuiltinType.DOUBLE; - default -> INT; + default -> INT; // 其他后缀当作无效,按 int 处理(如需严格,可改为抛/报“非法后缀”) }; } - if (digits.indexOf('.') >= 0 || digits.indexOf('e') >= 0 || digits.indexOf('E') >= 0) { - return BuiltinType.DOUBLE; + // 无后缀:包含小数点或 e/E → double;否则 int + if (looksLikeFloat(digits)) { + return BuiltinType.DOUBLE; // 浮点默认 double } return INT; } /** - * 检查字面量数值是否在推断类型范围内,超出范围则添加语义错误。 + * 做范围校验,发生越界时写入智能的错误与建议。 * - * @param ctx 语义分析上下文 - * @param node 当前字面量节点 - * @param raw 字面量原始字符串(含后缀) + * @param ctx 语义上下文(承载错误列表与日志) + * @param node 当前数字字面量节点 * @param inferred 推断类型 - * @param digits 去除后缀的纯数字部分 + * @param digits 规整后的数字主体(去下划线、去后缀、"123."→"123.0") */ 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); @@ -82,77 +105,224 @@ public class NumberLiteralAnalyzer implements ExpressionAnalyzer "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); + // 智能的错误描述与建议(header 使用 digits,避免带后缀) + String msg = getSmartSuggestionOneShot(digits, inferred); ctx.getErrors().add(new SemanticError(node, msg)); ctx.log("错误: " + msg); } } /** - * 根据类型名生成风格统一的溢出建议字符串。 + * 生成智能的错误提示与建议。 * - * @param raw 字面量原始字符串 - * @param digits 去除后缀的数字部分 - * @param typeName 推断类型的名称 - * @return 完整的错误描述 + *

策略:

+ *
    + *
  • BYTE/SHORT/INT:若能放进更大整型,直接建议;否则一次性提示已超出 int/long 范围;
  • + *
  • LONG:直接提示“超出 long 可表示范围。”;
  • + *
  • FLOAT:若 double 能放下 → 建议 double;否则一次性提示“超出 float/double 可表示范围。”;
  • + *
  • DOUBLE:直接提示“超出 double 可表示范围。”。
  • + *
+ * + * @param digits 去后缀后的数字主体(用于 header 与建议示例) + * @param inferred 推断类型 + * @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; + private static String getSmartSuggestionOneShot(String digits, Type inferred) { + String header; + String suggestion; + + switch (inferred) { + case BYTE -> { + long v; + try { + v = Long.parseLong(digits); + } catch (NumberFormatException e) { + // 连 long 都放不下:智能 + header = composeHeader(digits, "超出 byte/short/int/long 可表示范围。"); + return header; + } + if (v >= Short.MIN_VALUE && v <= Short.MAX_VALUE) { + header = composeHeader(digits, "超出 byte 可表示范围。"); + suggestion = "建议将变量类型声明为 short,并在数字末尾添加 's'(如 " + digits + "s)。"; + } else if (v >= Integer.MIN_VALUE && v <= Integer.MAX_VALUE) { + header = composeHeader(digits, "超出 byte 可表示范围。"); + suggestion = "建议将变量类型声明为 int(如 " + digits + ")。"; + } else { + header = composeHeader(digits, "超出 byte 可表示范围。"); + suggestion = "建议将变量类型声明为 long,并在数字末尾添加 'L'(如 " + digits + "L)。"; + } + return appendSuggestion(header, suggestion); + } + case SHORT -> { + long v; + try { + v = Long.parseLong(digits); + } catch (NumberFormatException e) { + header = composeHeader(digits, "超出 short/int/long 可表示范围。"); + return header; + } + if (v >= Integer.MIN_VALUE && v <= Integer.MAX_VALUE) { + header = composeHeader(digits, "超出 short 可表示范围。"); + suggestion = "建议将变量类型声明为 int(如 " + digits + ")。"; + } else { + header = composeHeader(digits, "超出 short 可表示范围。"); + suggestion = "建议将变量类型声明为 long,并在数字末尾添加 'L'(如 " + digits + "L)。"; + } + return appendSuggestion(header, suggestion); + } + case INT -> { + try { + // 尝试解析为 long:若成功,说明“能进 long” + Long.parseLong(digits); + } catch (NumberFormatException e) { + // 连 long 都放不下:智能 + header = composeHeader(digits, "超出 int/long 可表示范围。"); + return header; + } + // 能进 long:直接建议 long + header = composeHeader(digits, "超出 int 可表示范围。"); + suggestion = "建议将变量类型声明为 long,并在数字末尾添加 'L'(如 " + digits + "L)。"; + return appendSuggestion(header, suggestion); + } + case LONG -> { + // 已明确处于 long 分支且越界:智能 + header = composeHeader(digits, "超出 long 可表示范围。"); + return header; + } + case FLOAT -> { + // float 放不下:尝试 double,若能放下则直接建议 double;否则智能提示 float/double 都不行 + boolean fitsDouble = fitsDouble(digits); + if (fitsDouble) { + header = composeHeader(digits, "超出 float 可表示范围。"); + suggestion = "建议将变量类型声明为 double(如 " + digits + ")。"; // double 默认,无需 d 后缀 + return appendSuggestion(header, suggestion); + } else { + header = composeHeader(digits, "超出 float/double 可表示范围。"); + return header; + } + } + case DOUBLE -> { + header = composeHeader(digits, "超出 double 可表示范围。"); + return header; + } + default -> { + header = composeHeader(digits, "超出 " + inferred + " 可表示范围。"); + return header; + } + } } /** - * 判断字符串是否为浮点数格式(包含小数点或科学计数法)。 + * 生成越界错误头部:统一使用“数字主体”而非原始 raw(避免带后缀引起误导)。 * - * @param s 数字部分字符串 - * @return 是否为浮点格式 + * @param digits 去后缀后的数字主体 + * @param tail 错误尾部描述(如“超出 int 可表示范围。”) + */ + private static String composeHeader(String digits, String tail) { + return "数值字面量越界: \"" + digits + "\" " + tail; + } + + /** + * 在头部后拼接建议文本(若存在)。 + * + * @param header 错误头部 + * @param suggestion 建议文本(可能为 null) + */ + private static String appendSuggestion(String header, String suggestion) { + return suggestion == null ? header : header + " " + suggestion; + } + + /** + * 文本层面判断“是否看起来是浮点字面量”: + * 只要包含 '.' 或 'e/E',即视为浮点。 */ private static boolean looksLikeFloat(String s) { return s.indexOf('.') >= 0 || s.indexOf('e') >= 0 || s.indexOf('E') >= 0; } /** - * 对数字字面量进行语义分析,推断类型,并进行溢出与格式校验。 + * 文本判断“是否为零”(不解析,纯文本): + * 在遇到 e/E 之前,若出现任意 '1'..'9',视为非零;否则视为零。 + * 用于识别“文本非零但解析结果为 0.0”的下溢场景。 + *

+ * 例: + * "0.0" → true + * "000" → true + * "1e-9999" → false(e 前有 '1';若解析为 0.0 则视为下溢) + * "0e-9999" → true + */ + private static boolean isTextualZero(String s) { + if (s == null || s.isEmpty()) return false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == 'e' || c == 'E') break; // 指数部分不参与“是否为零”的判断 + if (c >= '1' && c <= '9') return true; + } + return false; + } + + /** + * 判断 double 是否能正常表示(不溢出、也非“文本非零但解析为 0.0”的下溢)。 * - * @param ctx 当前语义分析上下文(可用于错误收集等) - * @param mi 当前模块信息(未使用) - * @param fn 当前函数节点(未使用) - * @param locals 当前作用域的符号表(未使用) - * @param expr 数字字面量表达式节点 - * @return 推断出的类型 + * @param digits 去后缀后的数字主体 + */ + private static boolean fitsDouble(String digits) { + try { + double d = Double.parseDouble(digits); + return !Double.isInfinite(d) && !(d == 0.0 && isTextualZero(digits)); + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 规整数字串: + * 仅移除末尾的类型后缀(仅 b/s/l/f,大小写均可,不含 d/D)。 + * + * @param s 原始字面量字符串 + * @return 规整后的数字主体 + */ + private static String normalizeDigits(String s) { + if (s == null) return ""; + String t = s.trim(); + if (t.isEmpty()) return t; + + // 仅移除末尾的类型后缀(b/s/l/f,大小写均可) + char last = t.charAt(t.length() - 1); + if ("bBsSfFlL".indexOf(last) >= 0) { + t = t.substring(0, t.length() - 1).trim(); + } + return t; + } + + + /** + * 入口:对数字字面量进行语义分析。 + *

+ * 分步: + *

    + *
  1. 读取原始文本 raw;
  2. + *
  3. 识别是否带后缀(仅 b/s/l/f);
  4. + *
  5. 规整数字主体 digits(去下划线、去后缀、补小数点零);
  6. + *
  7. 按规则推断目标类型;
  8. + *
  9. 做范围校验,越界时记录智能的错误与建议;
  10. + *
  11. 返回推断类型。
  12. + *
*/ @Override public Type analyze(Context ctx, @@ -161,27 +331,30 @@ public class NumberLiteralAnalyzer implements ExpressionAnalyzer true; + case 'b', 's', 'l', 'f' -> true; default -> false; }; - String digits = hasSuffix ? raw.substring(0, raw.length() - 1) : raw; - // 推断类型 - Type inferred = inferType(hasSuffix, suffix, digits); + // 3) 规整数字主体 + String digitsNormalized = normalizeDigits(raw); - // 做范围校验,发现溢出等问题直接加到 ctx 错误列表 - validateRange(ctx, expr, raw, inferred, digits); + // 4) 推断类型 + Type inferred = inferType(hasSuffix, suffix, digitsNormalized); + + // 5) 范围校验(发生越界则收集智能的错误与建议) + validateRange(ctx, expr, inferred, digitsNormalized); return inferred; } + }