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 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 diff --git a/playground/BugFarm/Bug3/Main.snow b/playground/BugFarm/Bug3/Main.snow new file mode 100644 index 0000000..7918328 --- /dev/null +++ b/playground/BugFarm/Bug3/Main.snow @@ -0,0 +1,14 @@ +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 + + 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 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..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 @@ -5,50 +5,324 @@ 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. 首先检查字面量末尾是否带有类型后缀(不区分大小写): - * - *
  2. - *
  3. 若无后缀,则: - * - *
  4. + *
  5. 若字面量以类型后缀(b/s/l/f,大小写均可)结尾,则按后缀直接推断目标类型:
  6. + * + *
  7. 若无后缀,且文本包含小数点或科学计数法('.' 或 'e/E'),则推断为 double(浮点默认 double,不支持 d/D 后缀);
  8. + *
  9. 否则推断为 int。
  10. *
- * 本分析器不处理溢出、非法格式等诊断,仅做类型推断。 + * + *

智能错误提示策略:

+ * */ public class NumberLiteralAnalyzer implements ExpressionAnalyzer { /** - * 对数字字面量进行语义分析,推断其类型。 - *

- * 分析流程: - *

    - *
  1. 若字面量以后缀结尾,直接按后缀映射类型。
  2. - *
  3. 否则,若含有小数点或科学计数法标记,则为 double,否则为 int。
  4. - *
+ * 根据字面量后缀和文本内容推断类型。 * - * @param ctx 当前语义分析上下文(可用于记录诊断信息等,当前未使用) - * @param mi 当前模块信息(未使用) - * @param fn 当前函数节点(未使用) - * @param locals 当前作用域的符号表(未使用) - * @param expr 数字字面量表达式节点 - * @return 对应的 {@link BuiltinType},如 INT、DOUBLE 等 + * @param hasSuffix 是否存在类型后缀(仅 b/s/l/f 有效) + * @param suffix 后缀字符(已转小写) + * @param digits 去掉下划线与后缀后的数字主体(可能含 . 或 e/E) + * @return 推断类型(byte / short / int / long / float / double 之一) + */ + private static Type inferType(boolean hasSuffix, char suffix, String digits) { + if (hasSuffix) { + // 仅支持 b/s/l/f;不支持 d/D(浮点默认 double) + return switch (suffix) { + case 'b' -> BYTE; + case 's' -> SHORT; + case 'l' -> BuiltinType.LONG; + case 'f' -> BuiltinType.FLOAT; + default -> INT; // 其他后缀当作无效,按 int 处理(如需严格,可改为抛/报“非法后缀”) + }; + } + // 无后缀:包含小数点或 e/E → double;否则 int + if (looksLikeFloat(digits)) { + return BuiltinType.DOUBLE; // 浮点默认 double + } + return INT; + } + + /** + * 做范围校验,发生越界时写入智能的错误与建议。 + * + * @param ctx 语义上下文(承载错误列表与日志) + * @param node 当前数字字面量节点 + * @param inferred 推断类型 + * @param digits 规整后的数字主体(去下划线、去后缀、"123."→"123.0") + */ + private static void validateRange(Context ctx, + NumberLiteralNode node, + 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); + // 上溢:Infinity;下溢:解析为 0.0 但文本并非“全零” + if (Float.isInfinite(v) || (v == 0.0f && isTextualZero(digits))) { + throw new NumberFormatException("float overflow/underflow: " + digits); + } + } else if (inferred == BuiltinType.DOUBLE) { + double v = Double.parseDouble(digits); + if (Double.isInfinite(v) || (v == 0.0 && isTextualZero(digits))) { + throw new NumberFormatException("double overflow/underflow: " + digits); + } + } + } catch (NumberFormatException ex) { + // 智能的错误描述与建议(header 使用 digits,避免带后缀) + String msg = getSmartSuggestionOneShot(digits, inferred); + ctx.getErrors().add(new SemanticError(node, msg)); + ctx.log("错误: " + msg); + } + } + + /** + * 生成智能的错误提示与建议。 + * + *

策略:

+ * + * + * @param digits 去后缀后的数字主体(用于 header 与建议示例) + * @param inferred 推断类型 + * @return 完整错误消息(含建议) + */ + 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 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 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, @@ -57,41 +331,30 @@ public class NumberLiteralAnalyzer implements ExpressionAnalyzer true; + case 'b', 's', 'l', 'f' -> true; default -> false; }; - // 若有后缀,则 digits 为去除后缀的数字部分,否则为原文本 - String digits = hasSuffix ? raw.substring(0, raw.length() - 1) : raw; + // 3) 规整数字主体 + String digitsNormalized = normalizeDigits(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; // 理论上不会到这里 - }; - } + // 4) 推断类型 + Type inferred = inferType(hasSuffix, suffix, digitsNormalized); - // 2. 无后缀,根据文本是否含小数点或科学计数法(e/E)判断类型 - if (digits.indexOf('.') >= 0 || digits.indexOf('e') >= 0 || digits.indexOf('E') >= 0) { - return BuiltinType.DOUBLE; // 有小数/科学计数,默认 double 类型 - } + // 5) 范围校验(发生越界则收集智能的错误与建议) + validateRange(ctx, expr, inferred, digitsNormalized); - return BuiltinType.INT; // 否则为纯整数,默认 int 类型 + return inferred; } + }