From fdeaa36366e46e5a96d5c62e4716952f9b29e316 Mon Sep 17 00:00:00 2001 From: Luke Date: Wed, 30 Jul 2025 12:03:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E5=AD=97=E9=9D=A2=E9=87=8F=E7=9A=84=E8=AF=AD=E4=B9=89=E5=88=86?= =?UTF-8?q?=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. *
- * 本分析器不处理溢出、非法格式等诊断,仅做类型推断。 + *

+ * 校验规则如下: + *

*/ 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; } }