roots) {
+ for (Node n : roots) {
+ if (n instanceof ModuleNode mod) {
+ String moduleName = mod.name();
+ if (mod.globals() == null) continue;
+ for (DeclarationNode decl : mod.globals()) {
+ // 只处理 compile-time 的 const 常量,并要求有初始值
+ if (!decl.isConst() || decl.getInitializer().isEmpty()) continue;
+ ExpressionNode init = decl.getInitializer().get();
+ Object value = evalLiteral(init);
+ if (value != null) {
+ GlobalConstTable.register(moduleName + "." + decl.getName(), value);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 字面量提取与类型折叠工具。
+ * 用于将表达式节点还原为 Java 原生类型(int、long、double、String等),仅支持直接字面量。
+ *
+ * @param expr 要计算的表达式节点
+ * @return 提取到的原生常量值,不支持的情况返回 null
+ */
+ private Object evalLiteral(ExpressionNode expr) {
+ return switch (expr) {
+ case NumberLiteralNode num -> {
+ String raw = num.value();
+ String s = raw.replace("_", "");
+ char last = Character.toLowerCase(s.charAt(s.length() - 1));
+ String core = switch (last) {
+ case 'b', 's', 'l', 'f', 'd' -> s.substring(0, s.length() - 1);
+ default -> s;
+ };
+ try {
+ if (core.contains(".") || core.contains("e") || core.contains("E")) {
+ // 浮点数处理
+ yield Double.parseDouble(core);
+ }
+ long lv = Long.parseLong(core);
+ yield switch (last) {
+ case 'b' -> (byte) lv;
+ case 's' -> (short) lv;
+ case 'l' -> lv;
+ default -> (int) lv;
+ };
+ } catch (NumberFormatException ignore) {
+ yield null;
+ }
+ }
+ case StringLiteralNode str -> str.value();
+ case BoolLiteralNode b -> b.getValue() ? 1 : 0;
+ default -> null;
+ };
+ }
+
+ // ===================== IRFunction 构建辅助 =====================
+
/**
* 构建带有模块全局声明“注入”的函数,并将函数名加上模块前缀,保证模块内函数名唯一。
- *
- * 如果模块有全局声明,则这些声明会被插入到函数体前部。
+ * 如果模块有全局声明,则这些声明会被插入到函数体前部(**会过滤掉与参数同名的全局声明**)。
*
- * @param moduleNode 当前模块节点
- * @param functionNode 模块中的函数节点
- * @return 包含全局声明、已加前缀函数名的 IRFunction
+ * @param moduleNode 所属模块节点
+ * @param functionNode 待构建的函数节点
+ * @return 包含全局声明的 IRFunction
*/
private IRFunction buildFunctionWithGlobals(ModuleNode moduleNode, FunctionNode functionNode) {
// 拼接模块名和函数名,生成全限定名
String qualifiedName = moduleNode.name() + "." + functionNode.name();
- // 若无全局声明,仅重命名后直接构建
if (moduleNode.globals() == null || moduleNode.globals().isEmpty()) {
+ // 无全局声明,直接重命名构建
return buildFunction(renameFunction(functionNode, qualifiedName));
}
- // 若有全局声明,插入到函数体最前面
- List newBody = new ArrayList<>(moduleNode.globals().size() + functionNode.body().size());
- newBody.addAll(moduleNode.globals());
+
+ // ------- 过滤与参数重名的全局声明 -------
+ Set paramNames = new HashSet<>();
+ for (ParameterNode p : functionNode.parameters()) {
+ paramNames.add(p.name());
+ }
+ List filteredGlobals = new ArrayList<>();
+ for (DeclarationNode g : moduleNode.globals()) {
+ // 避免全局声明和参数重名,优先参数
+ if (!paramNames.contains(g.getName())) {
+ filteredGlobals.add(g);
+ }
+ }
+
+ if (filteredGlobals.isEmpty()) {
+ // 过滤后已无可插入的全局声明
+ return buildFunction(renameFunction(functionNode, qualifiedName));
+ }
+
+ // 合并全局声明与函数体,前插全局声明
+ List newBody = new ArrayList<>(filteredGlobals.size() + functionNode.body().size());
+ newBody.addAll(filteredGlobals);
newBody.addAll(functionNode.body());
FunctionNode wrapped = new FunctionNode(
qualifiedName,
@@ -85,11 +176,11 @@ public final class IRProgramBuilder {
}
/**
- * 生成一个重命名的 FunctionNode(只修改函数名,其他属性保持不变)。
+ * 生成一个重命名的 FunctionNode(只修改函数名,其他属性保持不变)。
*
* @param fn 原始函数节点
- * @param newName 新的函数名(通常为全限定名)
- * @return 重命名后的 FunctionNode
+ * @param newName 新的函数名(全限定名)
+ * @return 重命名后的函数节点
*/
private FunctionNode renameFunction(FunctionNode fn, String newName) {
return new FunctionNode(
@@ -104,8 +195,8 @@ public final class IRProgramBuilder {
/**
* 构建 IRFunction。
*
- * @param functionNode 要转换的函数节点
- * @return 转换结果 IRFunction
+ * @param functionNode 待构建的 FunctionNode
+ * @return 构建后的 IRFunction
*/
private IRFunction buildFunction(FunctionNode functionNode) {
return new FunctionBuilder().build(functionNode);
@@ -113,11 +204,10 @@ public final class IRProgramBuilder {
/**
* 将顶层语句节点封装成特殊的 "_start" 函数。
- *
- * 这对于脚本式文件支持至关重要(即文件最外层直接写语句)。
+ * 主要用于脚本模式支持,使得顶层语句也可以被 IR 执行引擎统一处理。
*
- * @param stmt 要封装的顶层语句
- * @return 包装成 FunctionNode 的 "_start" 函数
+ * @param stmt 顶层语句节点
+ * @return 封装后的 FunctionNode
*/
private FunctionNode wrapTopLevel(StatementNode stmt) {
return new FunctionNode(
diff --git a/src/main/java/org/jcnc/snow/compiler/ir/builder/InstructionFactory.java b/src/main/java/org/jcnc/snow/compiler/ir/builder/InstructionFactory.java
index 1c4dd8a..0d1fa69 100644
--- a/src/main/java/org/jcnc/snow/compiler/ir/builder/InstructionFactory.java
+++ b/src/main/java/org/jcnc/snow/compiler/ir/builder/InstructionFactory.java
@@ -6,23 +6,16 @@ import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
/**
- * InstructionFactory —— 统一生成并注册 IR 指令的工厂类。
- *
- * 该类封装了常见的 IR 指令生成方式,包括常量加载、二元运算、赋值、控制流等,
- * 统一简化指令插入和寄存器分配逻辑,提升 IR 生成阶段的代码可维护性和复用性。
- *
+ * IR 指令统一生成工厂类,负责封装常量加载、二元运算、赋值、控制流等指令生成逻辑。
+ * 提高 IR 生成阶段的可维护性与复用性。
*/
public class InstructionFactory {
- /* ====================================================================== */
- /* 常量 / 通用二元运算(新寄存器) */
- /* ====================================================================== */
-
/**
* 加载整数常量,将其写入一个新分配的虚拟寄存器,并返回该寄存器。
*
- * @param ctx 当前 IR 上下文(用于分配寄存器与添加指令)
- * @param value 要加载的整数常量值
+ * @param ctx 当前 IR 上下文
+ * @param value 整数常量值
* @return 存储该常量的新虚拟寄存器
*/
public static IRVirtualRegister loadConst(IRContext ctx, int value) {
@@ -31,88 +24,150 @@ public class InstructionFactory {
return r;
}
+ /**
+ * 加载 long 类型常量到新寄存器。
+ *
+ * @param ctx 当前 IR 上下文
+ * @param value long 类型常量值
+ * @return 存储该常量的新虚拟寄存器
+ */
+ public static IRVirtualRegister loadConst(IRContext ctx, long value) {
+ IRVirtualRegister r = ctx.newRegister();
+ ctx.addInstruction(new LoadConstInstruction(r, new IRConstant(value)));
+ return r;
+ }
+
+ /**
+ * 加载 float 类型常量到新寄存器。
+ *
+ * @param ctx 当前 IR 上下文
+ * @param value float 类型常量值
+ * @return 存储该常量的新虚拟寄存器
+ */
+ public static IRVirtualRegister loadConst(IRContext ctx, float value) {
+ IRVirtualRegister r = ctx.newRegister();
+ ctx.addInstruction(new LoadConstInstruction(r, new IRConstant(value)));
+ return r;
+ }
+
+ /**
+ * 加载 double 类型常量到新寄存器。
+ *
+ * @param ctx 当前 IR 上下文
+ * @param value double 类型常量值
+ * @return 存储该常量的新虚拟寄存器
+ */
+ public static IRVirtualRegister loadConst(IRContext ctx, double value) {
+ IRVirtualRegister r = ctx.newRegister();
+ ctx.addInstruction(new LoadConstInstruction(r, new IRConstant(value)));
+ return r;
+ }
+
/**
* 执行二元运算(如加法、减法等),结果写入新分配的虚拟寄存器并返回该寄存器。
*
* @param ctx 当前 IR 上下文
- * @param op 运算类型(IROpCode 枚举,如 ADD_I32 等)
- * @param a 第一个操作数寄存器
- * @param b 第二个操作数寄存器
- * @return 保存运算结果的新虚拟寄存器
+ * @param op 二元运算操作码
+ * @param a 左操作数寄存器
+ * @param b 右操作数寄存器
+ * @return 存储结果的新虚拟寄存器
*/
- public static IRVirtualRegister binOp(IRContext ctx, IROpCode op,
- IRVirtualRegister a, IRVirtualRegister b) {
+ public static IRVirtualRegister binOp(IRContext ctx, IROpCode op, IRVirtualRegister a, IRVirtualRegister b) {
IRVirtualRegister dest = ctx.newRegister();
ctx.addInstruction(new BinaryOperationInstruction(op, dest, a, b));
return dest;
}
- /* ====================================================================== */
- /* 直接写入指定寄存器 */
- /* ====================================================================== */
-
/**
- * 加载整数常量到指定虚拟寄存器。
+ * 加载常量到指定寄存器。
*
* @param ctx 当前 IR 上下文
- * @param dest 目标寄存器
- * @param value 要加载的整数常量
+ * @param dest 目标虚拟寄存器
+ * @param value IR 常量值
*/
public static void loadConstInto(IRContext ctx, IRVirtualRegister dest, IRConstant value) {
ctx.addInstruction(new LoadConstInstruction(dest, value));
}
/**
- * 对两个寄存器执行二元运算,将结果写入指定目标寄存器。
+ * 执行二元运算,并将结果写入指定寄存器。
*
* @param ctx 当前 IR 上下文
- * @param op 运算类型(IROpCode 枚举)
- * @param a 第一个操作数寄存器
- * @param b 第二个操作数寄存器
- * @param dest 运算结果目标寄存器
+ * @param op 二元运算操作码
+ * @param a 左操作数寄存器
+ * @param b 右操作数寄存器
+ * @param dest 目标虚拟寄存器
*/
- public static void binOpInto(IRContext ctx, IROpCode op,
- IRVirtualRegister a, IRVirtualRegister b,
- IRVirtualRegister dest) {
+ public static void binOpInto(IRContext ctx, IROpCode op, IRVirtualRegister a, IRVirtualRegister b, IRVirtualRegister dest) {
ctx.addInstruction(new BinaryOperationInstruction(op, dest, a, b));
}
/**
- * Move 指令(src → dest)。若寄存器相同也安全。
- *
- * 实现方式: dest = src + 0(即加上常量 0)。
- *
+ * 生成“值拷贝”语义(src → dest)。
+ * 若类型无法推断,默认采用 int 方案(ADD_I32, src+0)。
*
* @param ctx 当前 IR 上下文
* @param src 源寄存器
* @param dest 目标寄存器
*/
public static void move(IRContext ctx, IRVirtualRegister src, IRVirtualRegister dest) {
- // 自赋值无需任何操作,避免生成多余的常量 0 寄存器
if (src == dest) {
return;
}
- // 回退实现: dest = src + 0
- IRVirtualRegister zero = loadConst(ctx, 0);
- ctx.addInstruction(new BinaryOperationInstruction(IROpCode.ADD_I32, dest, src, zero));
+ String varType = ctx.getVarType(); // 需要 IRContext 提供
+ char suffix = '\0';
+ if (varType != null) {
+ switch (varType) {
+ case "byte" -> suffix = 'b';
+ case "short" -> suffix = 's';
+ case "int" -> suffix = 'i';
+ case "long" -> suffix = 'l';
+ case "float" -> suffix = 'f';
+ case "double" -> suffix = 'd';
+ }
+ }
+ IRVirtualRegister zero;
+ IROpCode op = switch (suffix) {
+ case 'd' -> {
+ zero = loadConst(ctx, 0.0);
+ yield IROpCode.ADD_D64;
+ }
+ case 'f' -> {
+ zero = loadConst(ctx, 0.0f);
+ yield IROpCode.ADD_F32;
+ }
+ case 'l' -> {
+ zero = loadConst(ctx, 0L);
+ yield IROpCode.ADD_L64;
+ }
+ case 's' -> {
+ zero = loadConst(ctx, 0);
+ yield IROpCode.ADD_S16;
+ }
+ case 'b' -> {
+ zero = loadConst(ctx, 0);
+ yield IROpCode.ADD_B8;
+ }
+ default -> {
+ zero = loadConst(ctx, 0);
+ yield IROpCode.ADD_I32;
+ }
+ };
+ ctx.addInstruction(new BinaryOperationInstruction(op, dest, src, zero));
}
- /* ====================================================================== */
- /* 控制流指令 */
- /* ====================================================================== */
-
/**
- * 生成无条件跳转(JMP)指令,跳转到指定标签。
+ * 生成无条件跳转指令。
*
* @param ctx 当前 IR 上下文
- * @param label 目标标签名
+ * @param label 跳转目标标签
*/
public static void jmp(IRContext ctx, String label) {
ctx.addInstruction(new IRJumpInstruction(label));
}
/**
- * 在 IR 中插入一个标签(Label)。
+ * 在 IR 流中插入标签。
*
* @param ctx 当前 IR 上下文
* @param label 标签名
@@ -122,22 +177,18 @@ public class InstructionFactory {
}
/**
- * 比较跳转(如 if a < b goto label),根据条件跳转到目标标签。
+ * 比较两个寄存器,并根据结果跳转到指定标签。
*
* @param ctx 当前 IR 上下文
- * @param cmp 比较操作码(如 IROpCode.LT_I32 等)
- * @param a 第一个操作数寄存器
- * @param b 第二个操作数寄存器
+ * @param cmp 比较操作码
+ * @param a 左操作数寄存器
+ * @param b 右操作数寄存器
* @param targetLabel 跳转目标标签
*/
- public static void cmpJump(IRContext ctx, IROpCode cmp,
- IRVirtualRegister a, IRVirtualRegister b,
- String targetLabel) {
+ public static void cmpJump(IRContext ctx, IROpCode cmp, IRVirtualRegister a, IRVirtualRegister b, String targetLabel) {
ctx.addInstruction(new IRCompareJumpInstruction(cmp, a, b, targetLabel));
}
- /* ---------------- 返回 ---------------- */
-
/**
* 生成返回指令(带返回值)。
*
@@ -149,7 +200,7 @@ public class InstructionFactory {
}
/**
- * 生成无返回值的 return 指令(如 void 函数)。
+ * 生成无返回值(void)返回指令。
*
* @param ctx 当前 IR 上下文
*/
diff --git a/src/main/java/org/jcnc/snow/compiler/ir/common/GlobalConstTable.java b/src/main/java/org/jcnc/snow/compiler/ir/common/GlobalConstTable.java
new file mode 100644
index 0000000..ad9795b
--- /dev/null
+++ b/src/main/java/org/jcnc/snow/compiler/ir/common/GlobalConstTable.java
@@ -0,0 +1,70 @@
+package org.jcnc.snow.compiler.ir.common;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 全局常量表,用于跨模块编译期常量查询和折叠。
+ *
+ *
+ * 主要功能:
+ *
+ * - 在 IRProgramBuilder 预扫描阶段,将所有模块级
const 常量
+ * (如 ModuleA.a)注册到全局常量表,支持跨模块访问。
+ * - 后续任何阶段均可通过 {@link #get(String)} 查询已注册常量,实现编译期常量折叠。
+ * - 保证线程安全,支持并发注册和访问。
+ *
+ *
+ * 常量的 key 格式为“模块名.常量名”,如 "ModuleA.a",以便唯一标识。
+ *
+ *
+ * 典型用法:
+ *
+ * GlobalConstTable.register("ModuleA.a", 10); // 注册常量
+ * Object val = GlobalConstTable.get("ModuleA.a"); // 查询常量
+ *
+ */
+public final class GlobalConstTable {
+
+ /** 存储全局常量: “ModuleName.constName” → 常量值。线程安全。 */
+ private static final Map CONSTS = new ConcurrentHashMap<>();
+
+ /**
+ * 工具类构造器,防止实例化。
+ */
+ private GlobalConstTable() { /* utility class */ }
+
+ /**
+ * 注册一个全局常量到表中(只在首次注册时生效,避免被覆盖)。
+ *
+ * @param qualifiedName 常量的全限定名(如 "ModuleA.a")
+ * @param value 常量的字面值(如 10、字符串、布尔等)
+ * @throws IllegalArgumentException 名称为 null 或空串时抛出
+ */
+ public static void register(String qualifiedName, Object value) {
+ if (qualifiedName == null || qualifiedName.isBlank()) {
+ throw new IllegalArgumentException("常量名不能为空");
+ }
+ CONSTS.putIfAbsent(qualifiedName, value);
+ }
+
+ /**
+ * 获取指定全局常量的值。
+ *
+ * @param qualifiedName 常量的全限定名(如 "ModuleA.a")
+ * @return 查到的常量值,如果未注册则返回 null
+ */
+ public static Object get(String qualifiedName) {
+ return CONSTS.get(qualifiedName);
+ }
+
+ /**
+ * 返回全部已注册常量的不可变视图(快照)。
+ * 注意:只读,不可修改。
+ *
+ * @return key=常量名,value=常量值的不可变 Map
+ */
+ public static Map all() {
+ return Map.copyOf(CONSTS);
+ }
+}
diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/core/LexerEngine.java b/src/main/java/org/jcnc/snow/compiler/lexer/core/LexerEngine.java
index 74f74f3..56cb63d 100644
--- a/src/main/java/org/jcnc/snow/compiler/lexer/core/LexerEngine.java
+++ b/src/main/java/org/jcnc/snow/compiler/lexer/core/LexerEngine.java
@@ -1,11 +1,9 @@
package org.jcnc.snow.compiler.lexer.core;
-import org.jcnc.snow.common.SnowConfig;
import org.jcnc.snow.compiler.lexer.base.TokenScanner;
import org.jcnc.snow.compiler.lexer.scanners.*;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
-import org.jcnc.snow.compiler.lexer.utils.TokenPrinter;
import java.io.File;
import java.util.ArrayList;
@@ -127,8 +125,8 @@ public class LexerEngine {
/**
* 目前包含2条规则:
- * 1. Declare-Ident declare 后必须紧跟合法标识符,并且只能一个
- * 2. Double-Ident declare 后若出现第二个 IDENTIFIER 视为多余
+ * 1. Declare-Ident declare 后必须紧跟合法标识符(或 const + 标识符),并且只能一个
+ * 2. Double-Ident declare 后若出现第二个多余的 IDENTIFIER
* 发现问题仅写入 {@link #errors},不抛异常。
*/
private void validateTokens() {
@@ -139,17 +137,28 @@ public class LexerEngine {
if (tok.getType() == TokenType.KEYWORD
&& "declare".equalsIgnoreCase(tok.getLexeme())) {
- // 第一个非 NEWLINE token
- Token id1 = findNextNonNewline(i);
+ // 找 declare 后第一个非 NEWLINE token
+ Token t1 = findNextNonNewline(i);
+
+ // 如果有 const,允许
+ boolean hasConst = t1 != null
+ && t1.getType() == TokenType.KEYWORD
+ && "const".equalsIgnoreCase(t1.getLexeme());
+ int identStartIdx = hasConst ? tokens.indexOf(t1) : i;
+
+ // 找下一个非 NEWLINE token,如果有 const,就找下一个
+ Token id1 = findNextNonNewline(identStartIdx);
+
+ // id1 必须是 IDENTIFIER
if (id1 == null || id1.getType() != TokenType.IDENTIFIER) {
errors.add(err(
- (id1 == null ? tok : id1),
- "declare 后必须跟合法标识符 (以字母或 '_' 开头)"
+ (id1 == null ? (hasConst ? t1 : tok) : id1),
+ "declare 后必须跟合法标识符 (可选 const 关键字)"
));
continue; // 若首标识符就错,后续检查可略
}
- // 检查是否有第二个 IDENTIFIER
+ // 检查是否有第二个多余的 IDENTIFIER
Token id2 = findNextNonNewline(tokens.indexOf(id1));
if (id2 != null && id2.getType() == TokenType.IDENTIFIER) {
errors.add(err(id2, "declare 声明中出现多余的标识符"));
diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenFactory.java b/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenFactory.java
index 1b0afe3..01713b3 100644
--- a/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenFactory.java
+++ b/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenFactory.java
@@ -26,9 +26,9 @@ public class TokenFactory {
* 语言的保留关键字集合。
*/
private static final Set KEYWORDS = Set.of
- ("module", "function", "params", "returns", "body", "end",
- "if", "then", "else", "loop", "declare", "return", "import", "init",
- "cond", "step", "globals", "break", "continue");
+ ("module", "function", "params", "returns", "body", "end",
+ "if", "then", "else", "loop", "declare", "return", "import", "init",
+ "cond", "step", "globals", "break", "continue", "const");
/**
* 内置类型名称集合,如 int、string 等。
diff --git a/src/main/java/org/jcnc/snow/compiler/parser/ast/DeclarationNode.java b/src/main/java/org/jcnc/snow/compiler/parser/ast/DeclarationNode.java
index dd5bea6..af2cdae 100644
--- a/src/main/java/org/jcnc/snow/compiler/parser/ast/DeclarationNode.java
+++ b/src/main/java/org/jcnc/snow/compiler/parser/ast/DeclarationNode.java
@@ -10,21 +10,26 @@ import java.util.Optional;
* {@code DeclarationNode} 表示抽象语法树(AST)中的变量声明语句节点。
*
* 变量声明用于在语法层引入新的标识符及其类型信息,
- * 通常格式为 {@code type name = initializer;},其中初始化表达式可省略。
*
*/
public class DeclarationNode implements StatementNode {
- /** 声明的变量名称 */
+ /** 声明的变量名称。 */
private final String name;
- /** 变量的数据类型(如 "int", "string") */
+ /** 变量的数据类型(如 "int", "string")。 */
private final String type;
- /** 可选的初始化表达式 */
+ /** 是否为常量声明(true 表示 const 变量,false 表示普通变量)。 */
+ private final boolean isConst;
+
+ /**
+ * 可选的初始化表达式。
+ * 如果未指定初始化表达式,则为 Optional.empty()。
+ */
private final Optional initializer;
- /** 节点上下文信息(包含行号、列号等) */
+ /** 节点上下文信息(如源码中的行号、列号等)。 */
private final NodeContext context;
/**
@@ -32,12 +37,14 @@ public class DeclarationNode implements StatementNode {
*
* @param name 变量名称
* @param type 变量类型字符串(如 "int"、"string")
+ * @param isConst 是否为常量声明
* @param initializer 可选初始化表达式,若为 {@code null} 表示未初始化
* @param context 节点上下文信息(包含行号、列号等)
*/
- public DeclarationNode(String name, String type, ExpressionNode initializer, NodeContext context) {
+ public DeclarationNode(String name, String type, boolean isConst, ExpressionNode initializer, NodeContext context) {
this.name = name;
this.type = type;
+ this.isConst = isConst;
this.initializer = Optional.ofNullable(initializer);
this.context = context;
}
@@ -60,6 +67,15 @@ public class DeclarationNode implements StatementNode {
return type;
}
+ /**
+ * 判断该声明是否为常量(const)。
+ *
+ * @return 如果为常量声明则返回 true,否则返回 false
+ */
+ public boolean isConst() {
+ return isConst;
+ }
+
/**
* 获取可选的初始化表达式。
*
diff --git a/src/main/java/org/jcnc/snow/compiler/parser/statement/DeclarationStatementParser.java b/src/main/java/org/jcnc/snow/compiler/parser/statement/DeclarationStatementParser.java
index 5e0470b..319986a 100644
--- a/src/main/java/org/jcnc/snow/compiler/parser/statement/DeclarationStatementParser.java
+++ b/src/main/java/org/jcnc/snow/compiler/parser/statement/DeclarationStatementParser.java
@@ -34,45 +34,51 @@ public class DeclarationStatementParser implements StatementParser {
*/
@Override
public DeclarationNode parse(ParserContext ctx) {
+ // 便捷引用词法 token 流
+ var tokens = ctx.getTokens();
+
// 获取当前 token 的行号、列号和文件名
- int line = ctx.getTokens().peek().getLine();
- int column = ctx.getTokens().peek().getCol();
+ int line = tokens.peek().getLine();
+ int column = tokens.peek().getCol();
String file = ctx.getSourceName();
// 声明语句必须以 "declare" 开头
- ctx.getTokens().expect("declare");
+ tokens.expect("declare");
+
+ // 是否声明为常量
+ boolean isConst = tokens.match("const");
// 获取变量名称(标识符)
- String name = ctx.getTokens()
+ String name = tokens
.expectType(TokenType.IDENTIFIER)
.getLexeme();
// 类型标注的冒号分隔符
- ctx.getTokens().expect(":");
+ tokens.expect(":");
// 获取变量类型(类型标识符)
StringBuilder type = new StringBuilder(
- ctx.getTokens()
+ tokens
.expectType(TokenType.TYPE)
.getLexeme()
);
// 消费多维数组类型后缀 "[]"
- while (ctx.getTokens().match("[")) {
- ctx.getTokens().expectType(TokenType.RBRACKET); // 必须配对
+ while (tokens.match("[")) {
+ tokens.expectType(TokenType.RBRACKET); // 必须配对
type.append("[]"); // 类型名称拼接 [],如 int[][] 等
}
// 可选初始化表达式(=号右侧)
ExpressionNode init = null;
- if (ctx.getTokens().match("=")) {
+ if (tokens.match("=")) {
init = new PrattExpressionParser().parse(ctx);
}
// 声明语句必须以换行符结尾
- ctx.getTokens().expectType(TokenType.NEWLINE);
+ tokens.expectType(TokenType.NEWLINE);
// 返回构建好的声明语法树节点
- return new DeclarationNode(name, type.toString(), init, new NodeContext(line, column, file));
+ return new DeclarationNode(name, type.toString(), isConst, init, new NodeContext(line, column, file));
}
}
diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/MemberExpressionAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/MemberExpressionAnalyzer.java
new file mode 100644
index 0000000..5080545
--- /dev/null
+++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/MemberExpressionAnalyzer.java
@@ -0,0 +1,94 @@
+package org.jcnc.snow.compiler.semantic.analyzers.expression;
+
+import org.jcnc.snow.compiler.parser.ast.FunctionNode;
+import org.jcnc.snow.compiler.parser.ast.IdentifierNode;
+import org.jcnc.snow.compiler.parser.ast.MemberExpressionNode;
+import org.jcnc.snow.compiler.parser.ast.base.NodeContext;
+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.Symbol;
+import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
+import org.jcnc.snow.compiler.semantic.type.BuiltinType;
+import org.jcnc.snow.compiler.semantic.type.Type;
+
+/**
+ * {@code MemberExpressionAnalyzer} 用于分析模块成员访问表达式的类型和语义。
+ *
+ *
+ * 当前实现支持 ModuleName.constOrVar 形式的跨模块常量/全局变量访问,
+ * 能根据目标模块的全局符号表,返回准确的类型信息,完全支持跨模块类型推断。
+ *
+ * 对于非模块成员的访问(如对象.属性、多级 a.b.c),暂不支持,遇到时将报告语义错误。
+ *
+ *
+ *
+ * 核心特性:
+ *
+ * - 校验模块是否存在、是否已导入(或自身);
+ * - 跨模块访问目标模块的全局符号表,查找指定成员符号及其类型;
+ * - 若成员不存在,报告“模块成员未定义”语义错误;
+ * - 暂不支持更复杂的对象成员访问,遇到将报“不支持的成员访问对象类型”错误。
+ *
+ *
+ */
+public class MemberExpressionAnalyzer implements ExpressionAnalyzer {
+
+ /**
+ * 语义分析模块成员访问表达式。
+ *
+ * @param ctx 全局语义分析上下文,持有所有模块及错误记录
+ * @param mi 当前模块信息(用于判断导入关系)
+ * @param fn 当前函数节点
+ * @param locals 当前局部符号表
+ * @param expr 当前要分析的成员表达式(如 ModuleA.a)
+ * @return 成员表达式的类型;出错时类型降级为 int,并记录语义错误
+ */
+ @Override
+ public Type analyze(Context ctx,
+ ModuleInfo mi,
+ FunctionNode fn,
+ SymbolTable locals,
+ MemberExpressionNode expr) {
+
+ ctx.log("检查成员访问: " + expr);
+
+ // -------- 仅支持 ModuleName.member 形式 --------
+ if (expr.object() instanceof IdentifierNode(String mod, NodeContext _)) {
+
+ // 1. 校验模块存在且已导入或为本模块自身
+ if (!ctx.getModules().containsKey(mod)
+ || (!mi.getImports().contains(mod) && !mi.getName().equals(mod))) {
+ ctx.getErrors().add(new SemanticError(expr,
+ "未知或未导入模块: " + mod));
+ ctx.log("错误: 未导入模块 " + mod);
+ return BuiltinType.INT;
+ }
+
+ // 2. 查找目标模块的全局符号表,精确返回成员类型
+ ModuleInfo target = ctx.getModules().get(mod);
+ SymbolTable globals = target.getGlobals();
+ if (globals != null) {
+ Symbol sym = globals.resolve(expr.member());
+ if (sym != null) {
+ return sym.type(); // 找到成员,返回其真实类型
+ }
+ }
+
+ // 3. 成员不存在,记录语义错误并类型降级
+ ctx.getErrors().add(new SemanticError(expr,
+ "模块成员未定义: " + mod + "." + expr.member()));
+ ctx.log("错误: 模块成员未定义 " + mod + "." + expr.member());
+ return BuiltinType.INT;
+ }
+
+ // -------- 其它对象成员(如 a.b.c)暂不支持 --------
+ ctx.getErrors().add(new SemanticError(expr,
+ "不支持的成员访问对象类型: "
+ + expr.object().getClass().getSimpleName()));
+ ctx.log("错误: 不支持的成员访问对象类型 "
+ + expr.object().getClass().getSimpleName());
+ return BuiltinType.INT;
+ }
+}
diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/AssignmentAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/AssignmentAnalyzer.java
index 3cd43f4..2ad7232 100644
--- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/AssignmentAnalyzer.java
+++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/AssignmentAnalyzer.java
@@ -6,22 +6,28 @@ import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
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.*;
+import org.jcnc.snow.compiler.semantic.symbol.Symbol;
+import org.jcnc.snow.compiler.semantic.symbol.SymbolKind;
+import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
import org.jcnc.snow.compiler.semantic.type.Type;
/**
- * {@code AssignmentAnalyzer} 是赋值语句的语义分析器。
- *
- * 负责分析和验证赋值语句的合法性,包括:
+ * {@code AssignmentAnalyzer} 负责对赋值语句进行语义校验。
+ *
+ *
校验要点
*
- * - 变量是否已声明且可赋值(必须为 {@link SymbolKind#VARIABLE} 类型);
- * - 赋值右值的类型是否与变量类型兼容;
- * - 是否允许进行数值类型的自动宽化转换(如 {@code int → float})。
+ * - 左值解析:确保标识符已声明;
+ * - 常量保护:禁止修改 {@link SymbolKind#CONSTANT};
+ * - 类型兼容:验证右值类型与目标类型兼容,若均为数值类型则允许宽化转换(如
int → double)。
*
- * 若类型不兼容且无法自动宽化,则将记录语义错误并输出日志信息。
+ *
+ * 任何不满足条件的情况都会向 {@link Context#getErrors()} 记录 {@link SemanticError}。
*/
public class AssignmentAnalyzer implements StatementAnalyzer {
+ /** 错误消息前缀,统一便于搜索定位 */
+ private static final String ERR_PREFIX = "赋值错误: ";
+
/**
* 分析赋值语句的语义有效性,包括左值合法性与类型匹配性。
*
@@ -41,29 +47,37 @@ public class AssignmentAnalyzer implements StatementAnalyzer {
// 获取赋值左值变量名并进行符号解析
ctx.log("赋值检查: " + asg.variable());
Symbol sym = locals.resolve(asg.variable());
-
- // 检查变量是否已声明且为可赋值的变量类型
- if (sym == null || sym.kind() != SymbolKind.VARIABLE) {
+ if (sym == null) {
ctx.getErrors().add(new SemanticError(asg,
- "未声明的变量: " + asg.variable()));
- ctx.log("错误: 未声明的变量 " + asg.variable());
+ ERR_PREFIX + "未声明的变量: " + asg.variable()));
+ ctx.log(ERR_PREFIX + "未声明的变量 " + asg.variable());
+ return; // 无需继续后续检查
+ }
+
+ /* ---------- 2. 常量不可修改 ---------- */
+ if (sym.kind() == SymbolKind.CONSTANT) {
+ ctx.getErrors().add(new SemanticError(asg,
+ ERR_PREFIX + "无法修改常量: " + asg.variable()));
+ ctx.log(ERR_PREFIX + "尝试修改常量 " + asg.variable());
return;
}
- // 分析右值表达式类型
- Type valType = ctx.getRegistry().getExpressionAnalyzer(asg.value())
+ /* ---------- 3. 右值类型分析 ---------- */
+ Type rhsType = ctx.getRegistry()
+ .getExpressionAnalyzer(asg.value())
.analyze(ctx, mi, fn, locals, asg.value());
- // 类型检查: 若类型不兼容,则尝试判断是否允许宽化转换
- if (!sym.type().isCompatible(valType)) {
- // 数值类型允许自动宽化转换(如 int → double)
- if (!(sym.type().isNumeric() && valType.isNumeric()
- && Type.widen(valType, sym.type()) == sym.type())) {
- ctx.getErrors().add(new SemanticError(asg,
- "赋值类型不匹配: 期望 " + sym.type()
- + ", 实际 " + valType));
- ctx.log("错误: 赋值类型不匹配 " + asg.variable());
- }
+ /* ---------- 4. 类型兼容性检查 ---------- */
+ boolean compatible = sym.type().isCompatible(rhsType);
+ boolean widenOK = sym.type().isNumeric()
+ && rhsType.isNumeric()
+ && Type.widen(rhsType, sym.type()) == sym.type();
+
+ if (!compatible && !widenOK) {
+ ctx.getErrors().add(new SemanticError(asg,
+ ERR_PREFIX + "类型不匹配: 期望 "
+ + sym.type() + ", 实际 " + rhsType));
+ ctx.log(ERR_PREFIX + "类型不匹配 " + asg.variable());
}
}
}
diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/DeclarationAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/DeclarationAnalyzer.java
index 6a5eb93..4a830fb 100644
--- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/DeclarationAnalyzer.java
+++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/DeclarationAnalyzer.java
@@ -6,7 +6,9 @@ import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
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.*;
+import org.jcnc.snow.compiler.semantic.symbol.Symbol;
+import org.jcnc.snow.compiler.semantic.symbol.SymbolKind;
+import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
import org.jcnc.snow.compiler.semantic.type.Type;
@@ -25,7 +27,7 @@ import org.jcnc.snow.compiler.semantic.type.Type;
public class DeclarationAnalyzer implements StatementAnalyzer {
/**
- * 对变量声明语句执行语义分析。
+ * 对单条声明语句执行语义分析。
*
* @param ctx 当前语义分析上下文对象,提供类型解析、错误记录、日志输出等功能。
* @param mi 当前模块信息,支持跨模块引用检查(本分析器未直接使用)。
@@ -40,45 +42,53 @@ public class DeclarationAnalyzer implements StatementAnalyzer {
SymbolTable locals,
DeclarationNode decl) {
- // 1. 解析声明类型
+ /* ---------- 1. 解析类型 ---------- */
Type varType = ctx.parseType(decl.getType());
if (varType == null) {
+ // 未知类型:记录错误并使用 int 兜底,避免后续空指针
ctx.getErrors().add(new SemanticError(decl,
"未知类型: " + decl.getType()));
ctx.log("错误: 未知类型 " + decl.getType()
- + " 在声明 " + decl.getName());
- varType = BuiltinType.INT; // 容错处理: 默认降级为 int
+ + " (声明 " + decl.getName() + ")");
+ varType = BuiltinType.INT;
}
- ctx.log("声明变量: " + decl.getName()
- + " 类型: " + varType);
+ ctx.log("声明" + (decl.isConst() ? "常量" : "变量")
+ + ": " + decl.getName() + " 类型: " + varType);
- // 2. 将变量注册到当前作用域符号表中,检查重复定义
- if (!locals.define(new Symbol(
- decl.getName(), varType, SymbolKind.VARIABLE
- ))) {
+ /* ---------- 2. 常量必须初始化 ---------- */
+ if (decl.isConst() && decl.getInitializer().isEmpty()) {
ctx.getErrors().add(new SemanticError(decl,
- "变量重复声明: " + decl.getName()));
- ctx.log("错误: 变量重复声明 " + decl.getName());
+ "常量必须在声明时初始化: " + decl.getName()));
+ // 继续分析以捕获更多错误
}
- // 3. 检查初始化表达式(如果存在)
- Type finalVarType = varType; // 用于 lambda 捕获
- decl.getInitializer().ifPresent(init -> {
- Type initType = ctx.getRegistry().getExpressionAnalyzer(init)
- .analyze(ctx, mi, fn, locals, init);
+ /* ---------- 3. 注册符号并检测重名 ---------- */
+ SymbolKind kind = decl.isConst() ? SymbolKind.CONSTANT
+ : SymbolKind.VARIABLE;
+ if (!locals.define(new Symbol(decl.getName(), varType, kind))) {
+ ctx.getErrors().add(new SemanticError(decl,
+ "重复声明: " + decl.getName()));
+ ctx.log("错误: 重复声明 " + decl.getName());
+ }
- // 检查类型是否兼容,或是否允许数值宽化转换
- if (!finalVarType.isCompatible(initType)) {
- boolean canWiden = finalVarType.isNumeric()
- && initType.isNumeric()
- && Type.widen(initType, finalVarType) == finalVarType;
- if (!canWiden) {
- ctx.getErrors().add(new SemanticError(decl,
- "初始化类型不匹配: 期望 " + finalVarType
- + ", 实际 " + initType));
- ctx.log("错误: 初始化类型不匹配 "
- + decl.getName());
- }
+ /* ---------- 4. 校验初始化表达式(若存在) ---------- */
+ Type finalVarType = varType;
+ decl.getInitializer().ifPresent(initExpr -> {
+ // 4.1 获取初始化表达式类型
+ Type initType = ctx.getRegistry()
+ .getExpressionAnalyzer(initExpr)
+ .analyze(ctx, mi, fn, locals, initExpr);
+
+ // 4.2 类型兼容性检查 + 数值宽化
+ boolean compatible = finalVarType.isCompatible(initType);
+ boolean widenOK = finalVarType.isNumeric()
+ && initType.isNumeric()
+ && Type.widen(initType, finalVarType) == finalVarType;
+
+ if (!compatible && !widenOK) {
+ ctx.getErrors().add(new SemanticError(decl,
+ "初始化类型不匹配: 期望 " + finalVarType + ", 实际 " + initType));
+ ctx.log("错误: 初始化类型不匹配 " + decl.getName());
}
});
}
diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/AnalyzerRegistrar.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/AnalyzerRegistrar.java
index f0b301a..74b4d3f 100644
--- a/src/main/java/org/jcnc/snow/compiler/semantic/core/AnalyzerRegistrar.java
+++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/AnalyzerRegistrar.java
@@ -65,8 +65,8 @@ public final class AnalyzerRegistrar {
// ---------- 注册一元表达式分析器 ----------
registry.registerExpressionAnalyzer(UnaryExpressionNode.class, new UnaryExpressionAnalyzer());
- // 对尚未实现的表达式类型使用兜底处理器
+ // ---------- 成员访问表达式 ----------
registry.registerExpressionAnalyzer(MemberExpressionNode.class,
- new UnsupportedExpressionAnalyzer<>());
+ new MemberExpressionAnalyzer());
}
}
diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/FunctionChecker.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/FunctionChecker.java
index 50f1ec6..c57bd07 100644
--- a/src/main/java/org/jcnc/snow/compiler/semantic/core/FunctionChecker.java
+++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/FunctionChecker.java
@@ -1,8 +1,8 @@
package org.jcnc.snow.compiler.semantic.core;
+import org.jcnc.snow.compiler.parser.ast.DeclarationNode;
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
import org.jcnc.snow.compiler.parser.ast.ModuleNode;
-import org.jcnc.snow.compiler.parser.ast.DeclarationNode;
import org.jcnc.snow.compiler.parser.ast.ReturnNode;
import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
import org.jcnc.snow.compiler.semantic.error.SemanticError;
@@ -11,70 +11,85 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolKind;
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
+import java.util.ArrayList;
+import java.util.List;
+
/**
- * {@code FunctionChecker} 是语义分析阶段中用于检查函数体语句合法性的调度器。
+ * {@code FunctionChecker} 是 Snow 编译器语义分析阶段用于检查所有函数体合法性的总控调度器。
*
- * 它逐个遍历所有模块中的函数定义,并对函数体中的每一条语句调用对应的语义分析器,
- * 执行类型检查、作用域验证、错误记录等任务。
- *
- * 核心职责包括:
+ * 设计核心:采用“两遍扫描”方案,彻底解决跨模块全局变量/常量类型推断和引用依赖问题:
*
- * - 为每个函数构建局部符号表并注册函数参数为变量;
- * - 分发函数体语句至相应的 {@link StatementAnalyzer};
- * - 记录未支持语句类型为语义错误;
- * - 依赖上下文 {@link Context} 提供模块信息、类型解析、错误收集等服务。
+ * - 第一遍:为所有模块预先构建并注册其全局符号表(globals),保证跨模块引用时可见。
+ * - 第二遍:在全局符号表全部就绪后,依次分析所有模块的函数体,实现局部作用域、类型推断、语义校验等任务。
+ *
+ * 功能职责:
+ *
+ * - 遍历所有模块,先建立 globals,再遍历并检查所有函数体语句。
+ * - 为每个函数体构建完整符号表,并注册参数变量。
+ * - 分发每条语句到对应 {@link StatementAnalyzer} 进行类型检查和错误校验。
+ * - 自动检查非 void 函数 return 完备性。
+ * - 记录所有语义错误,便于前端高亮和诊断。
*
*
- * @param ctx 全局语义分析上下文,提供模块信息、注册表、错误记录等支持
+ * @param ctx 全局语义分析上下文,持有模块信息、符号表、错误收集等资源
*/
public record FunctionChecker(Context ctx) {
/**
- * 构造函数体检查器。
- *
- * @param ctx 当前语义分析上下文
- */
- public FunctionChecker {
- }
-
- /**
- * 执行函数体检查流程。
+ * 主入口:对所有模块的所有函数体进行语义检查(两遍扫描实现)。
*
- * 对所有模块中的所有函数依次进行处理:
- *
- * - 查找模块对应的 {@link ModuleInfo};
- * - 创建函数局部符号表 {@link SymbolTable},并注册所有参数变量;
- * - 对函数体中的每一条语句分发到已注册的分析器进行语义分析;
- * - 若某条语句无可用分析器,则记录为 {@link SemanticError}。
- *
+ * 第一遍:为每个模块提前构建全局符号表(包含本模块所有全局变量和常量),
+ * 并注册到 {@link ModuleInfo},确保跨模块引用时所有全局符号都已可用。
+ *
+ * 第二遍:遍历所有模块的所有函数,对每个函数体:
+ *
+ * - 构建局部作用域,父作用域为对应模块的 globals;
+ * - 注册参数变量;
+ * - 依次分发每条语句到对应 {@link StatementAnalyzer},进行类型和语义检查;
+ * - 自动校验非 void 函数 return 完备性;
+ * - 将所有发现的问题统一记录到 {@link SemanticError} 列表。
+ *
*
* @param mods 所有模块的 AST 根节点集合
*/
public void check(Iterable mods) {
+ List moduleList = new ArrayList<>();
+ // ---------- 第1遍:收集所有全局符号表 ----------
for (ModuleNode mod : mods) {
- // 获取当前模块对应的语义信息
- ModuleInfo mi = ctx.modules().get(mod.name());
+ moduleList.add(mod);
- // 先构建全局符号表
+ // 获取当前模块的元信息
+ ModuleInfo mi = ctx.modules().get(mod.name());
+ // 创建本模块全局作用域(无父作用域)
SymbolTable globalScope = new SymbolTable(null);
+
+ // 注册所有全局变量/常量到符号表
for (DeclarationNode g : mod.globals()) {
var t = ctx.parseType(g.getType());
- // 检查全局变量是否重复声明
- if (!globalScope.define(new Symbol(g.getName(), t, SymbolKind.VARIABLE))) {
+ SymbolKind k = g.isConst() ? SymbolKind.CONSTANT : SymbolKind.VARIABLE;
+ String dupType = g.isConst() ? "常量" : "变量";
+ // 检查重复声明
+ if (!globalScope.define(new Symbol(g.getName(), t, k))) {
ctx.errors().add(new SemanticError(
g,
- "全局变量重复声明: " + g.getName()
+ dupType + "重复声明: " + g.getName()
));
}
}
+ // 注册到模块信息,供跨模块引用
+ mi.setGlobals(globalScope);
+ }
+
+ // ---------- 第2遍:遍历所有函数,分析函数体 ----------
+ for (ModuleNode mod : moduleList) {
+ ModuleInfo mi = ctx.modules().get(mod.name());
+ SymbolTable globalScope = mi.getGlobals();
- // 遍历模块中所有函数定义
for (FunctionNode fn : mod.functions()) {
-
- // 构建函数局部作用域符号表,父作用域为 globalScope
+ // 构建函数局部作用域,父作用域为 globalScope
SymbolTable locals = new SymbolTable(globalScope);
- // 将函数参数注册为局部变量
+ // 注册函数参数为局部变量
fn.parameters().forEach(p ->
locals.define(new Symbol(
p.name(),
@@ -83,7 +98,7 @@ public record FunctionChecker(Context ctx) {
))
);
- // 遍历并分析函数体内的每条语句
+ // 分析函数体内每条语句
for (var stmt : fn.body()) {
var analyzer = ctx.getRegistry().getStatementAnalyzer(stmt);
if (analyzer != null) {
diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/ModuleInfo.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/ModuleInfo.java
index 0761d50..d71dd42 100644
--- a/src/main/java/org/jcnc/snow/compiler/semantic/core/ModuleInfo.java
+++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/ModuleInfo.java
@@ -1,33 +1,55 @@
package org.jcnc.snow.compiler.semantic.core;
+import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
import org.jcnc.snow.compiler.semantic.type.FunctionType;
-import java.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
/**
* {@code ModuleInfo} 表示单个模块在语义分析阶段的元信息封装。
*
- * 用于在分析期间管理模块间依赖、函数签名查找等关键任务。
- * 每个模块对应一个唯一的 {@code ModuleInfo} 实例。
- *
- * 包含信息包括:
+ * 用于在分析期间管理模块间依赖、函数签名查找、全局符号表等关键任务。
+ * 每个模块对应一个唯一的 {@code ModuleInfo} 实例,贯穿整个语义分析流程。
+ *
+ *
包含信息:
*
- * - 模块名称(唯一标识);
- * - 该模块导入的其他模块名集合;
- * - 该模块中定义的所有函数签名 {@code Map}。
+ * - 模块名称(全局唯一标识);
+ * - 该模块导入的其他模块名集合(跨模块引用支持);
+ * - 该模块中定义的所有函数签名 {@code Map};
+ * - 模块级全局符号表 {@link SymbolTable}(常量 / 全局变量,支持跨模块类型推断)。
+ *
+ *
+ * 典型用途:
+ *
+ * - 用于函数签名类型查找、重名检测、跨模块引用校验等;
+ * - 全局符号表为类型检查与后端 IR 常量折叠等模块级分析提供支撑。
*
*/
public class ModuleInfo {
- /** 模块名称,作为全局唯一标识 */
+ /**
+ * 模块名称,作为全局唯一标识
+ */
private final String name;
- /** 该模块显式导入的模块名集合(用于跨模块访问符号) */
+ /**
+ * 该模块显式导入的模块名集合(用于跨模块访问符号)
+ */
private final Set imports = new HashSet<>();
- /** 该模块中定义的函数名 → 函数类型映射 */
+ /**
+ * 该模块中定义的函数名 → 函数类型映射
+ */
private final Map functions = new HashMap<>();
+ /**
+ * 模块级全局符号表(常量 / 全局变量)
+ */
+ private SymbolTable globals;
+
/**
* 构造模块信息对象。
*
@@ -49,7 +71,7 @@ public class ModuleInfo {
/**
* 获取该模块导入的模块名称集合。
*
- * 返回集合为内部数据的直接引用,调用方可通过 {@code add/remove} 方法动态维护导入信息。
+ * 返回集合为内部数据的直接引用,调用方可通过 {@code add}/{@code remove} 方法动态维护导入信息。
*
* @return 可变集合,包含所有导入模块名
*/
@@ -69,4 +91,26 @@ public class ModuleInfo {
return functions;
}
+ /**
+ * 获取模块的全局符号表(包含常量与全局变量)。
+ *
+ * 该符号表由语义分析的 FunctionChecker 阶段构建完成并注入。
+ * 提供跨模块类型检查、常量折叠等能力。
+ *
+ * @return 当前模块的全局符号表
+ */
+ public SymbolTable getGlobals() {
+ return globals;
+ }
+
+ /**
+ * 设置模块的全局符号表。
+ *
+ * 仅应由 FunctionChecker 在语义分析全局扫描阶段调用。
+ *
+ * @param globals 全局符号表实例
+ */
+ public void setGlobals(SymbolTable globals) {
+ this.globals = globals;
+ }
}
diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolKind.java b/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolKind.java
index 5034d59..6eef2c2 100644
--- a/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolKind.java
+++ b/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolKind.java
@@ -3,31 +3,47 @@ package org.jcnc.snow.compiler.semantic.symbol;
/**
* {@code SymbolKind} 枚举用于标识符号表中不同类型的命名实体。
*
- * 在语义分析过程中,编译器需要根据符号的种类(Kind)采用不同的处理策略:
+ * 在语义分析过程中,编译器需要根据符号的种类(Kind)采用不同的处理策略:
* 例如变量参与类型推导、函数用于调用匹配、模块用于跨作用域引用等。
+ *
*
- * 当前支持的符号种类包括:
+ * 当前支持的符号种类包括:
*
* - {@link #VARIABLE}: 变量符号(局部变量、全局变量、成员变量等);
+ * - {@link #CONSTANT}: 常量符号(只读、不可变的公开常量);
* - {@link #FUNCTION}: 函数符号(自由函数、方法、构造函数等);
* - {@link #MODULE}: 模块符号(代表命名空间、库或逻辑模块);
*
+ *
+ * 可根据需求扩展更多符号种类。
+ *
*/
public enum SymbolKind {
/**
* 变量符号,表示在作用域中声明的可赋值实体。
*
- * 包括函数参数、局部变量、全局变量、常量等,
+ * 包括函数参数、局部变量、全局变量、成员变量等。
* 分析器会基于其类型参与表达式类型校验和赋值检查。
+ *
*/
VARIABLE,
+ /**
+ * 常量符号,表示只读、不可变的公开常量。
+ *
+ * 常量在声明后不可被修改,仅可读取。常用于定义全局、模块级的常量值,
+ * 在类型推断、常量折叠等语义分析过程中单独处理。
+ *
+ */
+ CONSTANT,
+
/**
* 函数符号,表示可调用的过程实体。
*
* 包括普通函数、方法、构造器等。
* 用于函数签名注册、函数调用检查及返回值推导。
+ *
*/
FUNCTION,
@@ -35,6 +51,7 @@ public enum SymbolKind {
* 模块符号,表示一个命名空间或模块单元。
*
* 在跨模块调用、导入语句校验、作用域隔离中发挥作用。
+ *
*/
MODULE
}