!63 feat: 增强语义分析与全局常量处理

Merge pull request !63 from Luke/feature/add-constant
This commit is contained in:
Luke 2025-08-26 09:31:01 +00:00 committed by Gitee
commit 3595631e2c
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
29 changed files with 897 additions and 273 deletions

View File

@ -7,4 +7,12 @@
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="false" name="Demo14" type="Application" factoryName="Application" folderName="Demo">
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
<module name="Snow" />
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo14 -o target/Demo14" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

10
.run/Demo22.run.xml Normal file
View File

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Demo22" type="Application" factoryName="Application" folderName="Demo">
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
<module name="Snow" />
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo22 -o target/Demo22" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

10
.run/Demo23.run.xml Normal file
View File

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Demo23" type="Application" factoryName="Application" folderName="Demo">
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
<module name="Snow" />
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo23 -o target/Demo23 --debug" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

10
.run/Demo24.run.xml Normal file
View File

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Demo24" type="Application" factoryName="Application" folderName="Demo">
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
<module name="Snow" />
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo24 -o target/Demo24" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="build-project2tar.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build-project2tar.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -3,4 +3,8 @@
<envs />
<method v="2" />
</configuration>
<configuration default="false" name="build-release-all.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build-release-all.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -1,10 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="build_project2tar.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build_project2tar.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
<envs />
<method v="2" />
</configuration>
<configuration default="false" name="build_project2tar.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build_project2tar.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
module: Main
import: ModuleA,os
function: main
returns: void
body:
declare sum: int = ModuleA.a+2
os.print(sum+1)
end body
end function
end module
module: ModuleA
globals:
declare const a: int =10
end module

View File

@ -0,0 +1,11 @@
module: os
import: os
function: print
params:
declare i1: int
returns: void
body:
syscall("PRINT",i1)
end body
end function
end module

View File

@ -0,0 +1,14 @@
module: Main
import: ModuleA
function: main
returns: void
body:
declare sum: byte = ModuleA.a
end body
end function
end module
module: ModuleA
globals:
declare const a: byte = 2b
end module

View File

@ -0,0 +1,26 @@
module: Main
import: ModuleA,os
globals:
declare const c: int = 10
function: main
returns: void
body:
declare a: int = ModuleA.sum(c,2)
os.print(a)
end body
end function
end module
module: ModuleA
globals:
declare const a: int = 10
function: sum
params:
declare a: int
declare b: int
returns: int
body:
return a + b
end body
end function
end module

View File

@ -0,0 +1,11 @@
module: os
import: os
function: print
params:
declare i1: int
returns: void
body:
syscall("PRINT",i1)
end body
end function
end module

View File

@ -1,5 +1,6 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.common.GlobalConstTable;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
@ -48,6 +49,8 @@ public record ExpressionBuilder(IRContext ctx) {
if (reg == null) throw new IllegalStateException("未定义标识符: " + id.name());
yield reg;
}
// 模块常量 / 全局变量 ModuleA.a
case MemberExpressionNode mem -> buildMember(mem);
// 二元表达式 a+b, x==y
case BinaryExpressionNode bin -> buildBinary(bin);
// 函数/方法调用表达式
@ -62,6 +65,40 @@ public record ExpressionBuilder(IRContext ctx) {
};
}
/**
* 成员访问表达式构建
*
* @param mem 成员表达式节点
* @return 存储结果的虚拟寄存器
*/
private IRVirtualRegister buildMember(MemberExpressionNode mem) {
if (!(mem.object() instanceof IdentifierNode id)) {
throw new IllegalStateException("不支持的成员访问对象类型: "
+ mem.object().getClass().getSimpleName());
}
String qualified = id.name() + "." + mem.member();
/* 1) 尝试直接获取已有寄存器绑定 */
IRVirtualRegister reg = ctx.getScope().lookup(qualified);
if (reg != null) {
return reg;
}
/* 2) 折叠为编译期常量:先查作用域,再查全局常量表 */
Object v = ctx.getScope().getConstValue(qualified);
if (v == null) {
v = GlobalConstTable.get(qualified);
}
if (v != null) {
IRVirtualRegister r = ctx.newRegister();
ctx.addInstruction(new LoadConstInstruction(r, new IRConstant(v)));
return r;
}
throw new IllegalStateException("未定义的常量: " + qualified);
}
/* ───────────────── 写入指定寄存器 ───────────────── */
/**

View File

@ -1,5 +1,6 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.common.GlobalConstTable;
import org.jcnc.snow.compiler.ir.common.GlobalFunctionTable;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
@ -13,6 +14,13 @@ import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
* <p>
* 负责将语法树中的 FunctionNode 节点转化为可执行的 IRFunction
* 包含参数声明返回类型推断函数体语句转换等步骤
*
* <ul>
* <li>支持自动导入全局/跨模块常量使跨模块常量引用 ModuleA.a IR 阶段可用</li>
* <li>将函数形参声明为虚拟寄存器并注册到作用域便于后续指令生成</li>
* <li>根据返回类型设置表达式默认字面量类型保证 IR 层类型一致性</li>
* <li>遍历并转换函数体语句为 IR 指令</li>
* </ul>
*/
public class FunctionBuilder {
@ -21,11 +29,15 @@ public class FunctionBuilder {
* <p>
* 构建过程包括:
* <ol>
* <li>初始化 IRFunction 实例和上下文</li>
* <li>根据函数返回类型设置默认类型后缀便于表达式推断</li>
* <li>声明参数到作用域并为每个参数分配虚拟寄存器</li>
* <li>遍历并转换函数体内的每条语句为 IR 指令</li>
* <li>函数构建完成后清理默认类型后缀防止影响其他函数</li>
* <li>在全局函数表注册函数签名便于后续模块/语义分析阶段查询</li>
* <li>初始化 IRFunction 实例和 IRContext 上下文对象包含作用域与寄存器信息</li>
* <li>自动导入全局常量包括跨模块 const 变量到当前作用域
* 使成员访问如 ModuleA.a 可直接折叠为常量</li>
* <li>根据函数返回类型设置表达式推断的默认字面量类型后缀
* doubled, floatf避免类型不一致</li>
* <li>遍历声明形参每个参数分配虚拟寄存器并注册到作用域</li>
* <li>依次转换函数体中的每条语句为 IR 指令</li>
* <li>函数体转换完成后清理默认类型后缀防止影响后续函数构建</li>
* </ol>
*
* @param functionNode 表示函数定义的语法树节点
@ -33,44 +45,56 @@ public class FunctionBuilder {
*/
public IRFunction build(FunctionNode functionNode) {
// 在全局函数表中注册函数信息
// 1. 在全局函数表注册函数名与返回类型
// 方便其他阶段/模块调用类型检查
GlobalFunctionTable.register(functionNode.name(), functionNode.returnType());
// 0) 基本初始化: 创建 IRFunction 实例与对应上下文
// 2. 初始化 IRFunction 实例与上下文对象
// IRFunction: 表示该函数的中间代码容器
// IRContext: 负责作用域寄存器分配等编译上下文管理
IRFunction irFunction = new IRFunction(functionNode.name());
IRContext irContext = new IRContext(irFunction);
// 1) 把函数返回类型注入为默认类型后缀供表达式类型推断
// 3. 自动导入所有全局/跨模块常量到当前作用域
// 支持如 ModuleA.a 这样的常量访问/折叠参见 ExpressionBuilder
GlobalConstTable.all().forEach((k, v) ->
irContext.getScope().importExternalConst(k, v));
// 4. 根据函数返回类型设置默认类型后缀
// 例如返回类型为 double , 字面量表达式自动用 d 后缀
char _returnSuffix = switch (functionNode.returnType().toLowerCase()) {
case "double" -> 'd';
case "float" -> 'f';
case "long" -> 'l';
case "short" -> 's';
case "byte" -> 'b';
default -> '\0';
default -> '\0'; // 其它类型不设默认后缀
};
ExpressionUtils.setDefaultSuffix(_returnSuffix);
try {
// 2) 声明形参: 为每个参数分配虚拟寄存器并声明到作用域
// 5. 遍历函数参数列表
// - 为每个参数分配一个新的虚拟寄存器
// - 注册参数名类型寄存器到当前作用域
// - 添加参数寄存器到 IRFunction用于后续调用与指令生成
for (ParameterNode p : functionNode.parameters()) {
IRVirtualRegister reg = irFunction.newRegister(); // 新寄存器
irContext.getScope().declare(p.name(), p.type(), reg); // 变量名寄存器绑定
irFunction.addParameter(reg); // 添加到函数参数列表
IRVirtualRegister reg = irFunction.newRegister();
irContext.getScope().declare(p.name(), p.type(), reg);
irFunction.addParameter(reg);
}
// 3) 生成函数体 IR: 遍历每条语句逐一转化
// 6. 遍历函数体语句节点转换为 IR 指令
// StatementBuilder 负责将每条语句递归转换为 IR
StatementBuilder stmtBuilder = new StatementBuilder(irContext);
for (StatementNode stmt : functionNode.body()) {
stmtBuilder.build(stmt);
}
} finally {
// 4) 清除默认后缀避免影响后续函数的推断
// 7. 清理默认类型后缀防止影响后续其他函数的类型推断
ExpressionUtils.clearDefaultSuffix();
}
// 返回构建好 IRFunction
// 8. 返回构建完成 IRFunction
return irFunction;
}
}

View File

@ -15,30 +15,25 @@ import java.util.Map;
* <li>支持变量与虚拟寄存器的重新绑定与查找</li>
* <li>支持变量的类型信息记录与查询</li>
* <li>支持变量的编译期常量值记录与查询便于常量折叠等优化</li>
* <li>支持跨模块全局常量 ModuleA.a查找</li>
* </ul>
*/
final class IRBuilderScope {
/**
* 变量名到虚拟寄存器的映射表
* 用于跟踪所有声明和分配的变量
*/
/** 变量名到虚拟寄存器的映射表(本地变量) */
private final Map<String, IRVirtualRegister> vars = new HashMap<>();
/** 变量名到类型字符串的映射表 */
private final Map<String, String> varTypes = new HashMap<>();
/** 变量名到编译期常量值的映射表(本作用域) */
private final Map<String, Object> varConstValues = new HashMap<>();
/**
* 变量名到类型字符串的映射表
* 用于类型分析与推断
*/
private final Map<String, String> varTypes = new HashMap<>();
/**
* 变量名到编译期常量值的映射表
* 用于常量折叠优化 intstring数组等常量
*/
private final Map<String, Object> varConstValues = new HashMap<>();
/**
* 当前作用域所绑定的 IRFunction 实例
* 用于申请新的虚拟寄存器
* 额外存放跨模块导入的全局常量
* key 形如 "ModuleA.a" value 为其常量值
*/
private final Map<String, Object> externalConsts = new HashMap<>();
/** 当前作用域所绑定的 IRFunction 实例 */
private IRFunction fn;
/**
@ -118,7 +113,7 @@ final class IRBuilderScope {
// ---------------- 编译期常量相关接口 ----------------
/**
* 设置变量的编译期常量值
* 设置变量的编译期常量值本地变量
*
* @param name 变量名称
* @param value 常量值null 表示清除
@ -129,21 +124,38 @@ final class IRBuilderScope {
}
/**
* 获取变量的编译期常量值如没有则返回 null
* 获取变量的编译期常量值本地变量或导入的外部常量
* <br>
* 优先查找本地常量未命中再查外部 "ModuleA.a"
*
* @param name 变量名称
* @param name 变量名称"模块名.常量名"
* @return 编译期常量值 null
*/
Object getConstValue(String name) {
return varConstValues.get(name);
Object v = varConstValues.get(name);
if (v != null) return v;
// 支持跨模块常量/全局变量
return externalConsts.get(name);
}
/**
* 清除变量的编译期常量值绑定
* 清除变量的编译期常量值绑定本地
*
* @param name 变量名称
*/
void clearConstValue(String name) {
varConstValues.remove(name);
}
// ---------------- 跨模块常量导入支持 ----------------
/**
* 导入外部其他模块的全局常量/变量
*
* @param qualifiedName 形如 "ModuleA.a"
* @param value 其常量值
*/
void importExternalConst(String qualifiedName, Object value) {
externalConsts.put(qualifiedName, value);
}
}

View File

@ -1,24 +1,26 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.common.GlobalConstTable;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.core.IRProgram;
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
import org.jcnc.snow.compiler.parser.ast.ModuleNode;
import org.jcnc.snow.compiler.parser.ast.*;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.Node;
import org.jcnc.snow.compiler.parser.ast.base.NodeContext;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* IRProgramBuilder 负责将 AST 根节点(如模块函数顶层语句)转换为可执行的 IRProgram 实例
* <p>
* 主要职责:
* IRProgramBuilder 负责将 AST 顶层节点转换为可执行的 {@link IRProgram}
*
* <ul>
* <li>遍历 AST 根节点根据类型分别处理(模块函数顶层语句)</li>
* <li>对模块内的函数添加全限定名并在函数体前注入全局变量声明</li>
* <li>单独的顶层语句封装为特殊的 "_start" 函数</li>
* <li>预扫描所有模块 <code>declare const</code> 常量登记到全局常量表支持跨模块常量折叠</li>
* <li>对模块内的函数加上模块前缀保证命名唯一并将本模块全局声明注入到函数体前部</li>
* <li>独立顶层语句自动包装为特殊的 "_start" 函数脚本模式支持</li>
* </ul>
*/
public final class IRProgramBuilder {
@ -31,20 +33,23 @@ public final class IRProgramBuilder {
* @throws IllegalStateException 遇到不支持的顶层节点类型时抛出
*/
public IRProgram buildProgram(List<Node> roots) {
// 预先收集并登记全部模块常量到全局常量表
preloadGlobals(roots);
IRProgram irProgram = new IRProgram();
for (Node node : roots) {
switch (node) {
case ModuleNode moduleNode -> {
// 处理模块节点:遍历其中所有函数统一用模块名.函数名作为全限定名
// 处理模块节点遍历其中所有函数统一用模块名.函数名作为全限定名避免命名冲突
for (FunctionNode f : moduleNode.functions()) {
irProgram.add(buildFunctionWithGlobals(moduleNode, f));
}
}
case FunctionNode functionNode ->
// 处理顶层函数节点:直接构建为 IRFunction 并加入
// 处理顶层函数节点直接构建为 IRFunction 并加入
irProgram.add(buildFunction(functionNode));
case StatementNode statementNode ->
// 处理脚本式顶层语句:封装成 "_start" 函数后构建并添加
// 处理脚本式顶层语句封装成 "_start" 函数后构建并添加
irProgram.add(buildFunction(wrapTopLevel(statementNode)));
default ->
// 遇到未知类型节点抛出异常
@ -54,25 +59,111 @@ public final class IRProgramBuilder {
return irProgram;
}
// ===================== 全局常量收集 =====================
/**
* 扫描所有模块节点将其中声明的 const 全局变量compile-time 常量
* "模块名.常量名" 形式注册到全局常量表
* 支持跨模块常量折叠用于后续 IR 生成过程中的常量折叠优化
*
* @param roots AST 顶层节点列表
*/
private void preloadGlobals(List<Node> 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 原生类型intlongdoubleString等仅支持直接字面量
*
* @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 构建辅助 =====================
/**
* 构建带有模块全局声明注入的函数并将函数名加上模块前缀保证模块内函数名唯一
* <p>
* 如果模块有全局声明则这些声明会被插入到函数体前部
* 如果模块有全局声明则这些声明会被插入到函数体前部**会过滤掉与参数同名的全局声明**
*
* @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<StatementNode> newBody = new ArrayList<>(moduleNode.globals().size() + functionNode.body().size());
newBody.addAll(moduleNode.globals());
// ------- 过滤与参数重名的全局声明 -------
Set<String> paramNames = new HashSet<>();
for (ParameterNode p : functionNode.parameters()) {
paramNames.add(p.name());
}
List<StatementNode> 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<StatementNode> 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" 函数
* <p>
* 这对于脚本式文件支持至关重要(即文件最外层直接写语句)
* 主要用于脚本模式支持使得顶层语句也可以被 IR 执行引擎统一处理
*
* @param stmt 要封装的顶层语句
* @return 包装成 FunctionNode "_start" 函数
* @param stmt 顶层语句节点
* @return 封装后的 FunctionNode
*/
private FunctionNode wrapTopLevel(StatementNode stmt) {
return new FunctionNode(

View File

@ -6,23 +6,16 @@ import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
/**
* InstructionFactory 统一生成并注册 IR 指令的工厂类
* <p>
* 该类封装了常见的 IR 指令生成方式包括常量加载二元运算赋值控制流等
* 统一简化指令插入和寄存器分配逻辑提升 IR 生成阶段的代码可维护性和复用性
* </p>
* 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若寄存器相同也安全
* <p>
* 实现方式: dest = src + 0即加上常量 0
* </p>
* 生成值拷贝语义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 上下文
*/

View File

@ -0,0 +1,70 @@
package org.jcnc.snow.compiler.ir.common;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 全局常量表用于跨模块编译期常量查询和折叠
*
* <p>
* 主要功能
* <ul>
* <li> IRProgramBuilder 预扫描阶段将所有模块级 <code>const</code> 常量
* ModuleA.a注册到全局常量表支持跨模块访问</li>
* <li>后续任何阶段均可通过 {@link #get(String)} 查询已注册常量实现编译期常量折叠</li>
* <li>保证线程安全支持并发注册和访问</li>
* </ul>
* <p>
* 常量的 key 格式为模块名.常量名 "ModuleA.a"以便唯一标识
*
* <p>
* 典型用法
* <pre>
* GlobalConstTable.register("ModuleA.a", 10); // 注册常量
* Object val = GlobalConstTable.get("ModuleA.a"); // 查询常量
* </pre>
*/
public final class GlobalConstTable {
/** 存储全局常量: “ModuleName.constName” → 常量值。线程安全。 */
private static final Map<String, Object> 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);
}
/**
* 返回全部已注册常量的不可变视图快照
* <p>注意只读不可修改</p>
*
* @return key=常量名value=常量值的不可变 Map
*/
public static Map<String, Object> all() {
return Map.copyOf(CONSTS);
}
}

View File

@ -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条规则: <br>
* 1. Declare-Ident declare 后必须紧跟合法标识符并且只能一个<br>
* 2. Double-Ident declare 后若出现第二个 IDENTIFIER 视为多余<br>
* 1. Declare-Ident declare 后必须紧跟合法标识符 const + 标识符并且只能一个<br>
* 2. Double-Ident declare 后若出现第二个多余的 IDENTIFIER<br>
* <p>发现问题仅写入 {@link #errors}不抛异常</p>
*/
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 声明中出现多余的标识符"));

View File

@ -28,7 +28,7 @@ public class TokenFactory {
private static final Set<String> KEYWORDS = Set.of
("module", "function", "params", "returns", "body", "end",
"if", "then", "else", "loop", "declare", "return", "import", "init",
"cond", "step", "globals", "break", "continue");
"cond", "step", "globals", "break", "continue", "const");
/**
* 内置类型名称集合 intstring

View File

@ -10,21 +10,26 @@ import java.util.Optional;
* {@code DeclarationNode} 表示抽象语法树AST中的变量声明语句节点
* <p>
* 变量声明用于在语法层引入新的标识符及其类型信息
* 通常格式为 {@code type name = initializer;}其中初始化表达式可省略
* </p>
*/
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<ExpressionNode> 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;
}
/**
* 获取可选的初始化表达式
*

View File

@ -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));
}
}

View File

@ -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} 用于分析模块成员访问表达式的类型和语义
*
* <p>
* 当前实现支持 <code>ModuleName.constOrVar</code> 形式的跨模块常量/全局变量访问
* 能根据目标模块的全局符号表返回准确的类型信息完全支持跨模块类型推断
* <br>
* 对于非模块成员的访问如对象.属性多级 a.b.c暂不支持遇到时将报告语义错误
* </p>
*
* <p>
* <b>核心特性</b>
* <ul>
* <li>校验模块是否存在是否已导入或自身</li>
* <li>跨模块访问目标模块的全局符号表查找指定成员符号及其类型</li>
* <li>若成员不存在报告模块成员未定义语义错误</li>
* <li>暂不支持更复杂的对象成员访问遇到将报不支持的成员访问对象类型错误</li>
* </ul>
* </p>
*/
public class MemberExpressionAnalyzer implements ExpressionAnalyzer<MemberExpressionNode> {
/**
* 语义分析模块成员访问表达式
*
* @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;
}
}

View File

@ -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} 是赋值语句的语义分析器
* <p>
* 负责分析和验证赋值语句的合法性包括:
* {@code AssignmentAnalyzer} 负责对赋值语句进行语义校验
*
* <h2>校验要点</h2>
* <ul>
* <li>变量是否已声明且可赋值必须为 {@link SymbolKind#VARIABLE} 类型</li>
* <li>赋值右值的类型是否与变量类型兼容</li>
* <li>是否允许进行数值类型的自动宽化转换 {@code int float}</li>
* <li><b>左值解析</b>确保标识符已声明</li>
* <li><b>常量保护</b>禁止修改 {@link SymbolKind#CONSTANT}</li>
* <li><b>类型兼容</b>验证右值类型与目标类型兼容若均为数值类型则允许宽化转换 <code>int double</code></li>
* </ul>
* 若类型不兼容且无法自动宽化则将记录语义错误并输出日志信息
*
* 任何不满足条件的情况都会向 {@link Context#getErrors()} 记录 {@link SemanticError}
*/
public class AssignmentAnalyzer implements StatementAnalyzer<AssignmentNode> {
/** 错误消息前缀,统一便于搜索定位 */
private static final String ERR_PREFIX = "赋值错误: ";
/**
* 分析赋值语句的语义有效性包括左值合法性与类型匹配性
*
@ -41,29 +47,37 @@ public class AssignmentAnalyzer implements StatementAnalyzer<AssignmentNode> {
// 获取赋值左值变量名并进行符号解析
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())) {
/* ---------- 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,
"赋值类型不匹配: 期望 " + sym.type()
+ ", 实际 " + valType));
ctx.log("错误: 赋值类型不匹配 " + asg.variable());
}
ERR_PREFIX + "类型不匹配: 期望 "
+ sym.type() + ", 实际 " + rhsType));
ctx.log(ERR_PREFIX + "类型不匹配 " + asg.variable());
}
}
}

View File

@ -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<DeclarationNode> {
/**
* 变量声明语句执行语义分析
* 单条声明语句执行语义分析
*
* @param ctx 当前语义分析上下文对象提供类型解析错误记录日志输出等功能
* @param mi 当前模块信息支持跨模块引用检查本分析器未直接使用
@ -40,45 +42,53 @@ public class DeclarationAnalyzer implements StatementAnalyzer<DeclarationNode> {
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()
/* ---------- 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 (!canWiden) {
if (!compatible && !widenOK) {
ctx.getErrors().add(new SemanticError(decl,
"初始化类型不匹配: 期望 " + finalVarType
+ ", 实际 " + initType));
ctx.log("错误: 初始化类型不匹配 "
+ decl.getName());
}
"初始化类型不匹配: 期望 " + finalVarType + ", 实际 " + initType));
ctx.log("错误: 初始化类型不匹配 " + decl.getName());
}
});
}

View File

@ -65,8 +65,8 @@ public final class AnalyzerRegistrar {
// ---------- 注册一元表达式分析器 ----------
registry.registerExpressionAnalyzer(UnaryExpressionNode.class, new UnaryExpressionAnalyzer());
// 对尚未实现的表达式类型使用兜底处理器
// ---------- 成员访问表达式 ----------
registry.registerExpressionAnalyzer(MemberExpressionNode.class,
new UnsupportedExpressionAnalyzer<>());
new MemberExpressionAnalyzer());
}
}

View File

@ -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 编译器语义分析阶段用于检查所有函数体合法性的总控调度器
* <p>
* 它逐个遍历所有模块中的函数定义并对函数体中的每一条语句调用对应的语义分析器
* 执行类型检查作用域验证错误记录等任务
* <p>
* 核心职责包括:
* <b>设计核心</b>采用两遍扫描方案彻底解决跨模块全局变量/常量类型推断和引用依赖问题
* <ul>
* <li>为每个函数构建局部符号表并注册函数参数为变量</li>
* <li>分发函数体语句至相应的 {@link StatementAnalyzer}</li>
* <li>记录未支持语句类型为语义错误</li>
* <li>依赖上下文 {@link Context} 提供模块信息类型解析错误收集等服务</li>
* <li><b>第一遍</b>为所有模块预先构建并注册其全局符号表globals保证跨模块引用时可见</li>
* <li><b>第二遍</b>在全局符号表全部就绪后依次分析所有模块的函数体实现局部作用域类型推断语义校验等任务</li>
* </ul>
* <b>功能职责</b>
* <ul>
* <li>遍历所有模块先建立 globals再遍历并检查所有函数体语句</li>
* <li>为每个函数体构建完整符号表并注册参数变量</li>
* <li>分发每条语句到对应 {@link StatementAnalyzer} 进行类型检查和错误校验</li>
* <li>自动检查非 void 函数 return 完备性</li>
* <li>记录所有语义错误便于前端高亮和诊断</li>
* </ul>
*
* @param ctx 全局语义分析上下文提供模块信息注册表错误记录等支持
* @param ctx 全局语义分析上下文持有模块信息符号表错误收集等资源
*/
public record FunctionChecker(Context ctx) {
/**
* 构造函数体检查器
*
* @param ctx 当前语义分析上下文
*/
public FunctionChecker {
}
/**
* 执行函数体检查流程
* 主入口对所有模块的所有函数体进行语义检查两遍扫描实现
* <p>
* 对所有模块中的所有函数依次进行处理:
* <ol>
* <li>查找模块对应的 {@link ModuleInfo}</li>
* <li>创建函数局部符号表 {@link SymbolTable}并注册所有参数变量</li>
* <li>对函数体中的每一条语句分发到已注册的分析器进行语义分析</li>
* <li>若某条语句无可用分析器则记录为 {@link SemanticError}</li>
* </ol>
* <b>第一遍</b>为每个模块提前构建全局符号表包含本模块所有全局变量和常量
* 并注册到 {@link ModuleInfo}确保跨模块引用时所有全局符号都已可用
* <br>
* <b>第二遍</b>遍历所有模块的所有函数对每个函数体
* <ul>
* <li>构建局部作用域父作用域为对应模块的 globals</li>
* <li>注册参数变量</li>
* <li>依次分发每条语句到对应 {@link StatementAnalyzer}进行类型和语义检查</li>
* <li>自动校验非 void 函数 return 完备性</li>
* <li>将所有发现的问题统一记录到 {@link SemanticError} 列表</li>
* </ul>
*
* @param mods 所有模块的 AST 根节点集合
*/
public void check(Iterable<ModuleNode> mods) {
List<ModuleNode> 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) {

View File

@ -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} 表示单个模块在语义分析阶段的元信息封装
* <p>
* 用于在分析期间管理模块间依赖函数签名查找等关键任务
* 每个模块对应一个唯一的 {@code ModuleInfo} 实例
* <p>
* 包含信息包括:
* 用于在分析期间管理模块间依赖函数签名查找全局符号表等关键任务
* 每个模块对应一个唯一的 {@code ModuleInfo} 实例贯穿整个语义分析流程
*
* <p><b>包含信息</b>
* <ul>
* <li>模块名称唯一标识</li>
* <li>该模块导入的其他模块名集合</li>
* <li>该模块中定义的所有函数签名 {@code Map<String, FunctionType>}</li>
* <li>模块名称全局唯一标识</li>
* <li>该模块导入的其他模块名集合跨模块引用支持</li>
* <li>该模块中定义的所有函数签名 {@code Map<String, FunctionType>}</li>
* <li>模块级全局符号表 {@link SymbolTable}常量 / 全局变量支持跨模块类型推断</li>
* </ul>
*
* <p><b>典型用途</b>
* <ul>
* <li>用于函数签名类型查找重名检测跨模块引用校验等</li>
* <li>全局符号表为类型检查与后端 IR 常量折叠等模块级分析提供支撑</li>
* </ul>
*/
public class ModuleInfo {
/** 模块名称,作为全局唯一标识 */
/**
* 模块名称作为全局唯一标识
*/
private final String name;
/** 该模块显式导入的模块名集合(用于跨模块访问符号) */
/**
* 该模块显式导入的模块名集合用于跨模块访问符号
*/
private final Set<String> imports = new HashSet<>();
/** 该模块中定义的函数名 → 函数类型映射 */
/**
* 该模块中定义的函数名 函数类型映射
*/
private final Map<String, FunctionType> functions = new HashMap<>();
/**
* 模块级全局符号表常量 / 全局变量
*/
private SymbolTable globals;
/**
* 构造模块信息对象
*
@ -49,7 +71,7 @@ public class ModuleInfo {
/**
* 获取该模块导入的模块名称集合
* <p>
* 返回集合为内部数据的直接引用调用方可通过 {@code add/remove} 方法动态维护导入信息
* 返回集合为内部数据的直接引用调用方可通过 {@code add}/{@code remove} 方法动态维护导入信息
*
* @return 可变集合包含所有导入模块名
*/
@ -69,4 +91,26 @@ public class ModuleInfo {
return functions;
}
/**
* 获取模块的全局符号表包含常量与全局变量
* <p>
* 该符号表由语义分析的 FunctionChecker 阶段构建完成并注入
* 提供跨模块类型检查常量折叠等能力
*
* @return 当前模块的全局符号表
*/
public SymbolTable getGlobals() {
return globals;
}
/**
* 设置模块的全局符号表
* <p>
* 仅应由 FunctionChecker 在语义分析全局扫描阶段调用
*
* @param globals 全局符号表实例
*/
public void setGlobals(SymbolTable globals) {
this.globals = globals;
}
}

View File

@ -3,31 +3,47 @@ package org.jcnc.snow.compiler.semantic.symbol;
/**
* {@code SymbolKind} 枚举用于标识符号表中不同类型的命名实体
* <p>
* 在语义分析过程中编译器需要根据符号的种类Kind采用不同的处理策略:
* 在语义分析过程中编译器需要根据符号的种类Kind采用不同的处理策略
* 例如变量参与类型推导函数用于调用匹配模块用于跨作用域引用等
* </p>
* <p>
* 当前支持的符号种类包括:
* 当前支持的符号种类包括
* <ul>
* <li>{@link #VARIABLE}: 变量符号局部变量全局变量成员变量等</li>
* <li>{@link #CONSTANT}: 常量符号只读不可变的公开常量</li>
* <li>{@link #FUNCTION}: 函数符号自由函数方法构造函数等</li>
* <li>{@link #MODULE}: 模块符号代表命名空间库或逻辑模块</li>
* </ul>
* <p>
* 可根据需求扩展更多符号种类
* </p>
*/
public enum SymbolKind {
/**
* 变量符号表示在作用域中声明的可赋值实体
* <p>
* 包括函数参数局部变量全局变量常量等
* 包括函数参数局部变量全局变量成员变量等
* 分析器会基于其类型参与表达式类型校验和赋值检查
* </p>
*/
VARIABLE,
/**
* 常量符号表示只读不可变的公开常量
* <p>
* 常量在声明后不可被修改仅可读取常用于定义全局模块级的常量值
* 在类型推断常量折叠等语义分析过程中单独处理
* </p>
*/
CONSTANT,
/**
* 函数符号表示可调用的过程实体
* <p>
* 包括普通函数方法构造器等
* 用于函数签名注册函数调用检查及返回值推导
* </p>
*/
FUNCTION,
@ -35,6 +51,7 @@ public enum SymbolKind {
* 模块符号表示一个命名空间或模块单元
* <p>
* 在跨模块调用导入语句校验作用域隔离中发挥作用
* </p>
*/
MODULE
}