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}
+ *
+ *
职责:
+ *
+ * - 对数字字面量表达式进行语义分析,并推断其精确类型;
+ * - 按照推断类型进行范围校验;
+ * - 发生越界时,给出智能的错误提示与合理建议。
+ *
+ *
+ * 类型推断规则:
*
- * - 首先检查字面量末尾是否带有类型后缀(不区分大小写):
- *
- * - {@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})
- *
- *
- * - 若无后缀,则:
- *
- * - 只要文本中包含 {@code '.'} 或 {@code e/E},即判为 double 类型
- * - 否则默认判为 int 类型
- *
- *
+ * - 若字面量以类型后缀(b/s/l/f,大小写均可)结尾,则按后缀直接推断目标类型:
+ *
+ * - b → byte
+ * - s → short
+ * - l → long
+ * - f → float
+ *
+ * - 若无后缀,且文本包含小数点或科学计数法('.' 或 'e/E'),则推断为 double(浮点默认 double,不支持 d/D 后缀);
+ * - 否则推断为 int。
*
- * 本分析器不处理溢出、非法格式等诊断,仅做类型推断。
+ *
+ * 智能错误提示策略:
+ *
+ * - 整数:
+ *
+ * - int 放不下但 long 能放下 → 直接建议 long;
+ * - 连 long 也放不下 → 一次性提示“超出 int/long 可表示范围”。
+ *
+ *
+ * - 浮点:
+ *
+ * - float 放不下但 double 能放下 → 直接建议 double(无需 d 后缀);
+ * - 连 double 也放不下 → 一次性提示“超出 float/double 可表示范围”。
+ *
+ *
+ *
*/
public class NumberLiteralAnalyzer implements ExpressionAnalyzer {
/**
- * 对数字字面量进行语义分析,推断其类型。
- *
- * 分析流程:
- *
- * - 若字面量以后缀结尾,直接按后缀映射类型。
- * - 否则,若含有小数点或科学计数法标记,则为 double,否则为 int。
- *
+ * 根据字面量后缀和文本内容推断类型。
*
- * @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);
+ }
+ }
+
+ /**
+ * 生成智能的错误提示与建议。
+ *
+ * 策略:
+ *
+ * - BYTE/SHORT/INT:若能放进更大整型,直接建议;否则一次性提示已超出 int/long 范围;
+ * - LONG:直接提示“超出 long 可表示范围。”;
+ * - FLOAT:若 double 能放下 → 建议 double;否则一次性提示“超出 float/double 可表示范围。”;
+ * - DOUBLE:直接提示“超出 double 可表示范围。”。
+ *
+ *
+ * @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;
+ }
+
+
+ /**
+ * 入口:对数字字面量进行语义分析。
+ *
+ * 分步:
+ *
+ * - 读取原始文本 raw;
+ * - 识别是否带后缀(仅 b/s/l/f);
+ * - 规整数字主体 digits(去下划线、去后缀、补小数点零);
+ * - 按规则推断目标类型;
+ * - 做范围校验,越界时记录智能的错误与建议;
+ * - 返回推断类型。
+ *
*/
@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;
}
+
}