From 30fa84604b5b269d5bff1e2ce91fd93d19e54447 Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 30 Jul 2025 11:50:58 +0800 Subject: [PATCH 1/6] =?UTF-8?q?test:=20=E9=87=8D=E6=9E=84=20Bug2=20?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playground/BugFarm/Bug2/Main.snow | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/playground/BugFarm/Bug2/Main.snow b/playground/BugFarm/Bug2/Main.snow index ca1d0c8..674b74b 100644 --- a/playground/BugFarm/Bug2/Main.snow +++ b/playground/BugFarm/Bug2/Main.snow @@ -1,8 +1,7 @@ module: Main import: os function: main - parameter: - return_type: int + return_type: void body: loop: init: @@ -23,23 +22,12 @@ module: Main step: inter_j = inter_j + 1 body: - + end body end loop end body end loop - - return 0 - end body - end function - - function: print - parameter: - declare i1: int - return_type: void - body: - syscall("PRINT",i1) end body end function end module \ No newline at end of file From 3dfc8b63c8f4c75f03f025606698a0a4e066c29c Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 30 Jul 2025 11:54:34 +0800 Subject: [PATCH 2/6] =?UTF-8?q?test:=20=E6=B7=BB=E5=8A=A0=E6=95=B4?= =?UTF-8?q?=E6=95=B0=E6=BA=A2=E5=87=BA=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playground/BugFarm/Bug3/Main.snow | 19 +++++++++++++++++++ playground/BugFarm/Bug3/OS.snow | 11 +++++++++++ 2 files changed, 30 insertions(+) create mode 100644 playground/BugFarm/Bug3/Main.snow create mode 100644 playground/BugFarm/Bug3/OS.snow diff --git a/playground/BugFarm/Bug3/Main.snow b/playground/BugFarm/Bug3/Main.snow new file mode 100644 index 0000000..1e467bd --- /dev/null +++ b/playground/BugFarm/Bug3/Main.snow @@ -0,0 +1,19 @@ +module: Main + import: os + function: main + return_type: void + body: + // 合法 + declare b1: byte = 127b + declare s1: short = 32767s + declare i1: int = 2147483647 + declare l1: long = 9223372036854775807L + + // 溢出(都应该收集到错误) + declare b2: byte = 128b + declare s2: short = 40000s + declare i2: int = 3000000000 + declare l2: long = 9223372036854775808L + end body + end function +end module diff --git a/playground/BugFarm/Bug3/OS.snow b/playground/BugFarm/Bug3/OS.snow new file mode 100644 index 0000000..6026d43 --- /dev/null +++ b/playground/BugFarm/Bug3/OS.snow @@ -0,0 +1,11 @@ +module: os + import: os + function: print + parameter: + declare i1: int + return_type: void + body: + syscall("PRINT",i1) + end body + end function +end module \ No newline at end of file From fdeaa36366e46e5a96d5c62e4716952f9b29e316 Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 30 Jul 2025 12:03:41 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E6=95=B0?= =?UTF-8?q?=E5=AD=97=E5=AD=97=E9=9D=A2=E9=87=8F=E7=9A=84=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E5=88=86=E6=9E=90=E5=92=8C=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加类型推断和范围校验逻辑,支持 byte、short、int、long、float 和 double 类型 - 实现溢出和格式错误的统一处理,提供详细的错误提示 - 优化代码结构,增加私有方法以提高可读性和可维护性 --- .../expression/NumberLiteralAnalyzer.java | 186 +++++++++++++----- 1 file changed, 138 insertions(+), 48 deletions(-) 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 b26b2c7..c92b504 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 @@ -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.core.Context; 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.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; +import static org.jcnc.snow.compiler.semantic.type.BuiltinType.*; + /** - * {@code NumberLiteralAnalyzer} 用于对数字字面量表达式进行语义分析并推断其精确类型。 + * {@code NumberLiteralAnalyzer} 用于对数字字面量表达式进行语义分析并推断其精确类型,并支持类型范围校验与多错误收集。 *

- * 类型判定逻辑如下: + * 类型推断规则如下: *

    - *
  1. 首先检查字面量末尾是否带有类型后缀(不区分大小写): - *
      - *
    • {@code b}: byte 类型({@link BuiltinType#BYTE})
    • - *
    • {@code s}: short 类型({@link BuiltinType#SHORT})
    • - *
    • {@code l}: long 类型({@link BuiltinType#LONG})
    • - *
    • {@code f}: float 类型({@link BuiltinType#FLOAT})
    • - *
    • {@code d}: double 类型({@link BuiltinType#DOUBLE})
    • - *
    - *
  2. - *
  3. 若无后缀,则: - *
      - *
    • 只要文本中包含 {@code '.'} 或 {@code e/E},即判为 double 类型
    • - *
    • 否则默认判为 int 类型
    • - *
    - *
  4. + *
  5. 若字面量以类型后缀(b/s/l/f/d,大小写均可)结尾,则按后缀直接推断目标类型。
  6. + *
  7. 若无后缀,且文本包含小数点或科学计数法('.' 或 'e/E'),则推断为 double 类型。
  8. + *
  9. 否则推断为 int 类型。
  10. *
- * 本分析器不处理溢出、非法格式等诊断,仅做类型推断。 + *

+ * 校验规则如下: + *

    + *
  • 按推断类型解析字符串,若超出可表示范围或格式非法,则向语义上下文添加 {@link SemanticError},不抛异常,支持多错误收集和 IDE 友好提示。
  • + *
  • 所有建议信息风格统一,指引用户用更大类型的后缀或类型声明,最大类型提示为 double。
  • + *
*/ public class NumberLiteralAnalyzer implements ExpressionAnalyzer { /** - * 对数字字面量进行语义分析,推断其类型。 - *

- * 分析流程: - *

    - *
  1. 若字面量以后缀结尾,直接按后缀映射类型。
  2. - *
  3. 否则,若含有小数点或科学计数法标记,则为 double,否则为 int。
  4. - *
+ * 根据字面量后缀和文本内容推断类型。 * - * @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 fn 当前函数节点(未使用) * @param locals 当前作用域的符号表(未使用) * @param expr 数字字面量表达式节点 - * @return 对应的 {@link BuiltinType},如 INT、DOUBLE 等 + * @return 推断出的类型 */ @Override public Type analyze(Context ctx, @@ -60,38 +164,24 @@ public class NumberLiteralAnalyzer implements ExpressionAnalyzer true; default -> false; }; - - // 若有后缀,则 digits 为去除后缀的数字部分,否则为原文本 String digits = hasSuffix ? raw.substring(0, raw.length() - 1) : raw; - // 1. 若有后缀,直接返回对应类型 - if (hasSuffix) { - 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; // 理论上不会到这里 - }; - } + // 推断类型 + Type inferred = inferType(hasSuffix, suffix, digits); - // 2. 无后缀,根据文本是否含小数点或科学计数法(e/E)判断类型 - if (digits.indexOf('.') >= 0 || digits.indexOf('e') >= 0 || digits.indexOf('E') >= 0) { - return BuiltinType.DOUBLE; // 有小数/科学计数,默认 double 类型 - } + // 做范围校验,发现溢出等问题直接加到 ctx 错误列表 + validateRange(ctx, expr, raw, inferred, digits); - return BuiltinType.INT; // 否则为纯整数,默认 int 类型 + return inferred; } } From 9a2b5d5e4e992bcf7b9a9c2e00019230b6412409 Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 30 Jul 2025 12:03:50 +0800 Subject: [PATCH 4/6] =?UTF-8?q?test:=20=E7=A7=BB=E9=99=A4=20Bug3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=B8=AD=E7=9A=84=E6=BA=A2=E5=87=BA=E5=A3=B0=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playground/BugFarm/Bug3/Main.snow | 5 ----- 1 file changed, 5 deletions(-) diff --git a/playground/BugFarm/Bug3/Main.snow b/playground/BugFarm/Bug3/Main.snow index 1e467bd..7918328 100644 --- a/playground/BugFarm/Bug3/Main.snow +++ b/playground/BugFarm/Bug3/Main.snow @@ -9,11 +9,6 @@ module: Main declare i1: int = 2147483647 declare l1: long = 9223372036854775807L - // 溢出(都应该收集到错误) - declare b2: byte = 128b - declare s2: short = 40000s - declare i2: int = 3000000000 - declare l2: long = 9223372036854775808L end body end function end module From 1293ac1f8358f0e0f86adbe1344ba0c6e6b0d4bd Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 31 Jul 2025 00:26:44 +0800 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E6=95=B0?= =?UTF-8?q?=E5=AD=97=E5=AD=97=E9=9D=A2=E9=87=8F=E7=9A=84=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E5=88=86=E6=9E=90=E4=B8=8E=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构了类型推断逻辑,支持更精确的类型判断 - 实现了智能的错误提示策略,根据不同情况给出具体建议 - 优化了范围校验逻辑,提高了代码的健壮性 - 改进了代码结构和注释,提高了可读性和可维护性 --- .../expression/NumberLiteralAnalyzer.java | 337 +++++++++++++----- 1 file changed, 255 insertions(+), 82 deletions(-) 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} + * + *

职责: *

    - *
  • 按推断类型解析字符串,若超出可表示范围或格式非法,则向语义上下文添加 {@link SemanticError},不抛异常,支持多错误收集和 IDE 友好提示。
  • - *
  • 所有建议信息风格统一,指引用户用更大类型的后缀或类型声明,最大类型提示为 double。
  • + *
  • 对数字字面量表达式进行语义分析,并推断其精确类型;
  • + *
  • 按照推断类型进行范围校验;
  • + *
  • 发生越界时,给出智能的错误提示与合理建议。
  • + *
+ * + *

类型推断规则:

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

智能错误提示策略:

+ *
    + *
  • 整数: + *
      + *
    • int 放不下但 long 能放下 → 直接建议 long;
    • + *
    • 连 long 也放不下 → 一次性提示“超出 int/long 可表示范围”。
    • + *
    + *
  • + *
  • 浮点: + *
      + *
    • float 放不下但 double 能放下 → 直接建议 double(无需 d 后缀);
    • + *
    • 连 double 也放不下 → 一次性提示“超出 float/double 可表示范围”。
    • + *
    + *
  • *
*/ 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; } + } From f3409f32ce67c7f1b06d2a40e2117fbed302b59c Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 31 Jul 2025 00:26:56 +0800 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=20Bug3=20?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .run/Bug3.run.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .run/Bug3.run.xml diff --git a/.run/Bug3.run.xml b/.run/Bug3.run.xml new file mode 100644 index 0000000..a371f33 --- /dev/null +++ b/.run/Bug3.run.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file