feat: Initialize project skeleton, including basic directories and configuration files

This commit is contained in:
Luke 2025-06-06 15:53:19 +08:00
commit 0c2cb0357d
285 changed files with 20687 additions and 0 deletions

39
.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
.idea
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
/.idea/
/Snow.tar
/src/main/java/org/jcnc/snow/compiler/ir.tar

7
.run/Run.run.xml Normal file
View File

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run" type="CompoundRunConfigurationType">
<toRun name="build_project2tar.ps1" type="PowerShellRunType" />
<toRun name="SnowCompiler" type="Application" />
<method v="2" />
</configuration>
</component>

33
.run/SnowCompiler.run.xml Normal file
View File

@ -0,0 +1,33 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SnowCompiler" type="Application" factoryName="Application" activateToolWindowBeforeRun="false" nameIsGenerated="true">
<option name="ALTERNATIVE_JRE_PATH" value="23" />
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.compiler.cli.SnowCompiler" />
<module name="SCompiler" />
<option name="PROGRAM_PARAMETERS" value="test" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.jcnc.snow.compiler.parser.preprocessor.lexer.impl.api.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="false" name="SnowCompiler" type="Application" factoryName="Application" activateToolWindowBeforeRun="false" nameIsGenerated="true">
<option name="ALTERNATIVE_JRE_PATH" value="graalvm-ce-23" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.compiler.cli.SnowCompiler" />
<module name="Snow" />
<option name="PROGRAM_PARAMETERS" value="-d playground" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.jcnc.snow.compiler.parser.preprocessor.lexer.impl.api.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,10 @@
<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>

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,26 @@
$tarName = "Snow.tar"
# 脚本当前目录build文件夹
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
# 上一级目录snow根目录
$parentDir = Split-Path -Parent $scriptDir
# tar包的完整路径
$tarPath = Join-Path $parentDir $tarName
# 如果存在旧tar包删除
if (Test-Path $tarPath) {
Remove-Item $tarPath
}
Write-Output "正在创建新的 $tarName$parentDir ..."
# 打包:先切换到 org\jcnc 目录下再压缩 snow 文件夹
& tar -cf $tarPath -C "$scriptDir\..\src\main\java\org\jcnc" snow
if (Test-Path $tarPath) {
Write-Output "✅ 成功创建 $tarName"
} else {
Write-Output "❌ 创建失败,请检查 tar 命令"
}

5
build/readme.md Normal file
View File

@ -0,0 +1,5 @@
使用 build——project2tar.ps1 需要在管理员权限下的 PowerShell 输入下面的内容
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Tip:RemoteSigned 表示:本地创建的脚本可以运行,从互联网下载的脚本必须有签名。

View File

@ -0,0 +1,34 @@
module: Math
function: main
parameter:
return_type: int
body:
Math.factorial(6)
return 0
end body
end function
function: factorial
parameter:
declare n:int
return_type: int
body:
declare num1:int = 1
loop:
initializer:
declare counter:int = 1
condition:
counter <= n
update:
counter = counter + 1
body:
num1 = num1 * counter
end body
end loop
return num1
end body
end function
end module

18
lib/test/Dead_loop.snow Normal file
View File

@ -0,0 +1,18 @@
function: main
parameter:
return_type: int
body:
loop:
initializer:
declare i:int = 0
condition:
1 == 1
update:
i = i + 1
body:
end body
end loop
return 0
end body
end function

22
lib/test/opcode.snow Normal file
View File

@ -0,0 +1,22 @@
module: Math
function: factorial
parameter:
declare n:int
return_type: int
body:
declare num1:int = 1
loop:
initializer:
declare counter:int = 1
condition:
counter <= n
update:
counter = counter + 1
body:
num1 = num1 * counter
end body
end loop
return num1
end body
end function
end module

13
playground/main.snow Normal file
View File

@ -0,0 +1,13 @@
module: Main
import:Math
function: main
parameter:
return_type: int
body:
Math.factorial(6L,1L)
return 0
end body
end function
end module

11
playground/test.snow Normal file
View File

@ -0,0 +1,11 @@
module: Math
function: factorial
parameter:
declare n1: long
declare n2: long
return_type: long
body:
return n1+n2
end body
end function
end module

72
pom.xml Normal file
View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jcnc.snow</groupId>
<artifactId>Snow</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<native.maven.plugin.version>0.10.5</native.maven.plugin.version>
</properties>
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>org.jcnc.snow.compiler.cli.SnowCompiler</mainClass>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

1
readme.md Normal file
View File

@ -0,0 +1 @@
## Snow

View File

@ -0,0 +1,6 @@
module org.jcnc.snow.compiler {
requires java.desktop;
requires java.logging;
exports org.jcnc.snow.compiler.ir.core;
exports org.jcnc.snow.compiler.ir.instruction;
}

View File

@ -0,0 +1,77 @@
package org.jcnc.snow.compiler.backend.alloc;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.HashMap;
import java.util.Map;
/**
* 线性扫描寄存器分配器
* <p>
* 本类为 IR中间表示中的虚拟寄存器分配物理槽号通常用于后端生成目标代码时确定
* 各虚拟寄存器实际对应的物理寄存器或栈槽号采用简单的线性扫描分配策略
* </p>
* <p>
* 分配过程如下
* <ol>
* <li>优先为函数参数分配槽号 0 开始按参数顺序递增</li>
* <li>遍历函数体的每条指令为尚未分配的目标寄存器及其操作数分配新的槽号</li>
* <li>保证每个虚拟寄存器在函数作用域内分配唯一且连续的槽号</li>
* </ol>
* </p>
*/
public final class RegisterAllocator {
/**
* 虚拟寄存器到槽号的分配映射表
* <p>
* 虚拟寄存器 {@link IRVirtualRegister}
* 对应分配的槽编号 {@link Integer}
* </p>
*/
private final Map<IRVirtualRegister, Integer> map = new HashMap<>();
/**
* 为指定 IR 函数分配所有虚拟寄存器的槽号
* <p>
* 分配顺序说明
* <ol>
* <li>首先为所有参数分配槽号</li>
* <li>然后线性遍历函数体为每个指令涉及的虚拟寄存器目标或操作数分配槽号
* 遇到未分配的寄存器立即分配下一个可用槽号</li>
* </ol>
* 返回的映射不可变防止外部修改
* </p>
*
* @param fn 需要进行寄存器分配的 IR 函数对象
* @return 一个不可变映射记录所有虚拟寄存器到槽号的分配关系
*/
public Map<IRVirtualRegister, Integer> allocate(IRFunction fn) {
int next = 0; // 下一个可分配的槽编号
// 1. 优先为所有参数分配槽号顺序与参数列表一致
for (IRVirtualRegister param : fn.parameters()) {
map.put(param, next++);
}
// 2. 线性扫描整个函数体依次为指令涉及的虚拟寄存器分配槽号
for (IRInstruction inst : fn.body()) {
// 2.1 若指令有目标寄存器且尚未分配则分配新槽号
if (inst.dest() != null && !map.containsKey(inst.dest())) {
map.put(inst.dest(), next++);
}
// 2.2 遍历所有操作数若为虚拟寄存器且尚未分配则分配新槽号
for (IRValue operand : inst.operands()) {
if (operand instanceof IRVirtualRegister vr && !map.containsKey(vr)) {
map.put(vr, next++);
}
}
}
// 3. 返回不可变映射防止外部代码误修改分配结果
return Map.copyOf(map);
}
}

View File

@ -0,0 +1,91 @@
package org.jcnc.snow.compiler.backend.builder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 虚拟机代码生成器VMCodeGenerator
* <p>
* 本类作为指令生成器调度中心不负责任何具体 IR 指令到 VM 指令的转换实现
* 仅负责根据指令类型分发到对应的 {@link InstructionGenerator} 子类完成实际生成
* </p>
* <p>
* 工作流程简述
* <ol>
* <li>接收一组已注册的 IR 指令生成器并建立类型到生成器的映射表</li>
* <li>遍历 IR 函数体的每条指令根据类型找到对应的生成器调用其 generate 方法生成 VM 指令</li>
* <li>生成流程以函数为单位beginFunction/endFunction</li>
* </ol>
*/
public final class VMCodeGenerator {
/**
* 指令类型到生成器的注册表调度表
* <p>
* IR 指令类型Class对象
* 对应的指令生成器实例
* </p>
*/
private final Map<Class<? extends IRInstruction>, InstructionGenerator<? extends IRInstruction>> registry;
/**
* 虚拟寄存器到槽号的映射表 RegisterAllocator 负责生成
*/
private final Map<IRVirtualRegister, Integer> slotMap;
/**
* 虚拟机程序构建器用于输出 VM 指令
*/
private final VMProgramBuilder out;
/**
* 当前处理的函数名用于部分指令生成逻辑如主函数判断等
*/
private String currentFn;
/**
* 构造方法
*
* @param slotMap 虚拟寄存器到槽号的映射
* @param out 虚拟机程序构建器
* @param generators 各类 IR 指令生成器集合需预先构建
*/
public VMCodeGenerator(Map<IRVirtualRegister, Integer> slotMap,
VMProgramBuilder out,
List<InstructionGenerator<? extends IRInstruction>> generators) {
this.slotMap = slotMap;
this.out = out;
// 按类型注册各 IR 指令生成器建立不可变类型-生成器映射表
this.registry = generators.stream()
.collect(Collectors.toUnmodifiableMap(InstructionGenerator::supportedClass, g -> g));
}
/**
* 为一个 IR 函数生成虚拟机指令
*
* @param fn 待生成的 IR 函数对象
* @throws IllegalStateException 若遇到不支持的 IR 指令类型
*/
public void generate(IRFunction fn) {
this.currentFn = fn.name();
out.beginFunction(currentFn); // 输出函数起始
for (IRInstruction ins : fn.body()) {
@SuppressWarnings("unchecked")
// 取得与当前 IR 指令类型匹配的生成器泛型强转消除类型警告
InstructionGenerator<IRInstruction> gen =
(InstructionGenerator<IRInstruction>) registry.get(ins.getClass());
if (gen == null) {
throw new IllegalStateException("Unsupported IR: " + ins);
}
// 通过多态分发到实际生成器
gen.generate(ins, out, slotMap, currentFn);
}
out.endFunction(); // 输出函数结束
}
}

View File

@ -0,0 +1,195 @@
package org.jcnc.snow.compiler.backend.builder;
import org.jcnc.snow.vm.engine.VMOpCode;
import java.util.*;
/**
* VMProgramBuilder构建线性 VM 程序即按顺序存放所有 VM 指令
* <p>
* 本类用于编译器后端将所有生成的 VM 指令包括分支和调用指令统一存储管理
* 支持符号如函数入口标签地址的延迟解析与回填fix-up机制
* 可在目标尚未定义时提前生成分支或调用指令定义后自动修正
* </p>
* <p>
* 常用于处理跨函数跨标签的 CALL/JUMP 等复杂控制流确保最终生成的 VM 指令地址一致正确
* </p>
*/
public final class VMProgramBuilder {
/** 未解析目标的 CALL 指令信息(待修补) */
private record CallFix(int index, String target, int nArgs) {}
/** 未解析目标的分支指令JUMP/IC_* 等待修补) */
private record BranchFix(int index, String label) {}
/** 占位符:用于表示尚未确定的符号地址 */
private static final String PLACEHOLDER = "-1";
/** 按顺序存放的 VM 指令文本 */
private final List<String> code = new ArrayList<>();
// 虚拟机槽位编号到数据类型前缀的映射 0 -> 'I', 1 -> 'D'
private final Map<Integer, Character> slotType = new HashMap<>();
/** 符号如函数名标签名到其首地址即指令序号/偏移量的映射表
* 主要用于跳转和调用定位具体的代码位置 */
private final Map<String, Integer> addr = new HashMap<>();
/** 所有待回填fix-up CALL 调用指令记录
* 由于被调用目标地址在编译时可能尚未确定需要先记录最终统一回填 */
private final List<CallFix> callFixes = new ArrayList<>();
/** 所有待回填fix-up的分支跳转指令记录
* CALL 类似分支指令的目标地址也可能需要编译后期再补充 */
private final List<BranchFix> branchFixes = new ArrayList<>();
/** 程序计数器Program Counter表示下一个生成指令将插入的位置 */
private int pc = 0;
/**
* 设置某个槽位对应的数据类型前缀
* @param slot 槽位编号
* @param prefix 类型前缀 'I' 表示 int'D' 表示 double
*/
public void setSlotType(int slot, char prefix) {
slotType.put(slot, prefix);
}
/**
* 获取某个槽位对应的数据类型前缀
* 若未指定则返回默认类型 'I'int
* @param slot 槽位编号
* @return 类型前缀 'I', 'D'
*/
public char getSlotType(int slot) {
return slotType.getOrDefault(slot, 'I');
}
/**
* 标记函数入口或标签并尝试修补所有等候该符号的指令
* @param name 符号名函数名/标签名
*/
public void beginFunction(String name) {
addr.put(name, pc); // 记录当前地址为入口
patchCallFixes(name); // 修补所有待该符号的 CALL
patchBranchFixes(name); // 修补所有待该符号的分支
}
/**
* 结束函数当前实现为空方便 API 统一
*/
public void endFunction() { /* no-op */ }
/**
* 写入一条 VM 指令或标签
* <ul>
* <li>如果以冒号结尾视为标签仅登记其地址不写入指令流也不递增 pc</li>
* <li>否则写入实际指令并自增 pc</li>
* </ul>
* @param line 一行 VM 指令文本或标签名结尾有冒号
*/
public void emit(String line) {
if (line.endsWith(":")) { // 是标签定义行
String label = line.substring(0, line.length() - 1);
addr.put(label, pc); // 记录标签地址
patchBranchFixes(label); // 修补所有以该标签为目标的分支指令
return; // 标签行不写入 code不递增 pc
}
code.add(line); // 普通指令写入 code
pc++;
}
/**
* 生成 CALL 指令
* 支持延迟修补若目标已知直接写入地址否则写入占位并登记 fix-up
* @param target 目标函数名
* @param nArgs 参数个数
*/
public void emitCall(String target, int nArgs) {
Integer a = resolve(target);
if (a != null) {
emit(VMOpCode.CALL + " " + a + " " + nArgs);
} else {
emit(VMOpCode.CALL + " " + PLACEHOLDER + " " + nArgs);
callFixes.add(new CallFix(pc - 1, target, nArgs));
}
}
/**
* 生成分支JUMP IC_*指令
* 支持延迟修补机制
* @param opcode 指令名
* @param label 目标标签名
*/
public void emitBranch(String opcode, String label) {
Integer a = resolve(label);
if (a != null) {
emit(opcode + ' ' + a);
} else {
emit(opcode + ' ' + PLACEHOLDER);
branchFixes.add(new BranchFix(pc - 1, label));
}
}
/**
* 构建最终 VM 代码文本列表
* <ul>
* <li>若存在未解析符号CALL 或分支则抛出异常</li>
* <li>否则返回不可变指令流</li>
* </ul>
* @return 完整 VM 指令流
* @throws IllegalStateException 若有未修补的符号引用
*/
public List<String> build() {
if (!callFixes.isEmpty() || !branchFixes.isEmpty()) {
throw new IllegalStateException(
"Unresolved symbols — CALL: " + callFixes +
", BRANCH: " + branchFixes);
}
return List.copyOf(code);
}
/**
* 解析符号地址若全限定名找不到则降级尝试简单名
* @param sym 符号名
* @return 地址或 null未定义
*/
private Integer resolve(String sym) {
Integer a = addr.get(sym);
if (a == null && sym.contains(".")) {
a = addr.get(sym.substring(sym.lastIndexOf('.') + 1));
}
return a;
}
/**
* 修补所有以 name 为目标的 CALL 占位符
* @param name 新定义的函数名
*/
private void patchCallFixes(String name) {
for (Iterator<CallFix> it = callFixes.iterator(); it.hasNext();) {
CallFix f = it.next();
// 目标函数名完全匹配或后缀匹配兼容全限定名
if (f.target.equals(name) || f.target.endsWith("." + name)) {
code.set(f.index, VMOpCode.CALL + " " + addr.get(name) + " " + f.nArgs);
it.remove();
}
}
}
/**
* 修补所有以 label 为目标的分支占位符
* @param label 新定义的标签名
*/
private void patchBranchFixes(String label) {
for (Iterator<BranchFix> it = branchFixes.iterator(); it.hasNext();) {
BranchFix f = it.next();
if (f.label.equals(label)) {
String patched = code.get(f.index).replace(PLACEHOLDER, addr.get(label).toString());
code.set(f.index, patched);
it.remove();
}
}
}
}

View File

@ -0,0 +1,34 @@
package org.jcnc.snow.compiler.backend.core;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 通用指令生成器接口
* <p>
* 本接口规定了所有 IR 指令生成器翻译器必须实现的方法负责将特定类型的 IR 指令
* 翻译为虚拟机VM指令每个具体的指令生成器都需要指定其支持的 IR 指令类型并实现翻译生成的方法
*
* @param <T> 指令生成器所支持的 IRInstruction 子类型
*/
public interface InstructionGenerator<T extends IRInstruction> {
/**
* 获取当前生成器支持的 IR 指令类型
*
* @return 当前生成器支持的 IRInstruction 类对象
*/
Class<T> supportedClass();
/**
* 将一条 IR 指令翻译为对应的 VM 指令序列
*
* @param ins 当前待翻译的 IR 指令
* @param out 虚拟机程序构建器用于输出 VM 指令
* @param slotMap 虚拟寄存器与实际槽号的映射关系
* @param currentFn 当前函数名称用于作用域或调试等
*/
void generate(T ins, VMProgramBuilder out, Map<IRVirtualRegister, Integer> slotMap, String currentFn);
}

View File

@ -0,0 +1,56 @@
# Snow Compiler - 后端模块
> Snow 编译器的后端模块 —— 负责将中间表示IR翻译、优化并生成虚拟机指令序列VM
## 项目简介
**后端模块Backend** 是 [Snow 编译器]() 的核心组成部分之一承接中间表示IR与运行时虚拟机VM之间的桥梁。
它主要完成以下工作:
1. **寄存器分配**:将 IR 中的虚拟寄存器映射为物理槽号或栈槽;
2. **指令生成与调度**:为每条 IR 指令分配对应的 VM 指令生成器,并负责调用它们输出指令文本;
3. **程序构建**管理函数入口、标签定义、延迟修补fix-upCALL/JUMP 等控制流指令地址;
4. **操作码与常量映射**:提供 IR 操作码到 VM 操作码的映射工具,以及根据常量类型选择 PUSH/STORE 指令。
后端模块设计注重可扩展性:各类 IR → VM 的转换逻辑被拆分到不同的 `*Generator` 类中,新增指令只需注册新的生成器;寄存器分配和程序构建也各司其职,职责清晰,便于维护和测试。
## 核心功能
* **线性扫描寄存器分配**`RegisterAllocator` 按参数优先、指令顺序为 IR 虚拟寄存器分配连续槽号。
* **指令生成器体系**`InstructionGenerator<T>` 接口 + 多个 `*Generator` 实现,支持二元运算、跳转、调用、标签、常量加载、一元运算、返回等指令。
* **生成器调度**`VMCodeGenerator` 建立类型到生成器的映射,遍历 IR 函数体并分发调用,实现多态化指令生成。
* **程序构建与延迟修补**`VMProgramBuilder` 维护指令流、符号地址表及待修补列表,实现对 `CALL`/`JUMP` 等指令的延迟地址填充。
* **操作码与常量映射**`IROpCodeMapper` 提供 IR 操作码到 VM 指令名的静态映射;`OpHelper` 基于反射获取 VMOpCode 常量并根据值类型I/L/F…选择合适的 PUSH/STORE 操作码。
## 模块结构
```
backend/
├── core/
│ └── InstructionGenerator.java // 通用指令生成器接口
├── generator/
│ ├── BinaryOpGenerator.java // 二元运算
│ ├── UnaryOpGenerator.java // 一元运算
│ ├── LoadConstGenerator.java // 常量加载
│ ├── CallGenerator.java // 函数调用
│ ├── ReturnGenerator.java // 函数返回
│ ├── CmpJumpGenerator.java // 条件跳转
│ ├── JumpGenerator.java // 无条件跳转
│ └── LabelGenerator.java // 标签定义
├── alloc/
│ └── RegisterAllocator.java // 线性扫描寄存器分配
├── builder/
│ ├── VMCodeGenerator.java // 指令生成调度
│ └── VMProgramBuilder.java // VM 程序构建与 fix-up
└── util/
├── IROpCodeMapper.java // IR → VM 操作码映射
└── OpHelper.java // opcode 反射 & 常量指令辅助
```
## 开发环境
* JDK 23 或更高版本
* Maven 构建管理
* 推荐 IDEIntelliJ IDEA
---

View File

@ -0,0 +1,133 @@
package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.util.OpHelper;
import org.jcnc.snow.compiler.ir.instruction.BinaryOperationInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 二元运算指令生成器
* 支持二元运算指令的自动类型提升
* <p>类型提升优先级为D > F > L > I > S > B</p>
*/
public class BinaryOpGenerator implements InstructionGenerator<BinaryOperationInstruction> {
/* ---------- 类型优先级工具 ---------- */
/**
* 返回类型前缀的优先级数值数值越大类型越宽
* D: 6, F: 5, L: 4, I: 3, S: 2, B: 1
*/
private static int rank(char p) {
return switch (p) {
case 'D' -> 6;
case 'F' -> 5;
case 'L' -> 4;
case 'I' -> 3;
case 'S' -> 2;
case 'B' -> 1;
default -> 0;
};
}
/**
* 返回a和b中优先级更高的类型前缀即类型提升结果
*/
private static char promote(char a, char b) {
return rank(a) >= rank(b) ? a : b;
}
/**
* 类型前缀转字符串方便拼接
*/
private static String str(char p) {
return String.valueOf(p);
}
/* ---------- 类型转换指令工具 ---------- */
/**
* 根据源类型和目标类型前缀返回相应的类型转换指令助记符
*
* @param from 源类型前缀
* @param to 目标类型前缀
* @return 转换指令字符串 "I2L" "F2D"若无需转换则返回null
*/
private static String convert(char from, char to) {
if (from == to) return null; // 类型一致无需转换
return switch ("" + from + to) {
case "IL" -> "I2L";
case "ID" -> "I2D";
case "IF" -> "I2F";
case "LI" -> "L2I";
case "LD" -> "L2D";
case "LF" -> "L2F";
case "FI" -> "F2I";
case "FL" -> "F2L";
case "FD" -> "F2D";
case "DI" -> "D2I";
case "DL" -> "D2L";
case "DF" -> "D2F";
case "SI" -> "S2I";
case "BI" -> "B2I";
default -> null; // 其它组合暂未用到
};
}
/**
* 返回本生成器支持的指令类型 BinaryOperationInstruction
*/
@Override
public Class<BinaryOperationInstruction> supportedClass() {
return BinaryOperationInstruction.class;
}
/**
* 生成二元运算的虚拟机指令实现自动类型提升和必要的类型转换
*
* @param ins 当前二元运算IR指令
* @param out 虚拟机程序构建器
* @param slotMap IR虚拟寄存器到实际槽位编号的映射
* @param currentFn 当前函数名
*/
@Override
public void generate(BinaryOperationInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
/* ------- 1. 获取左右操作数的槽位编号和类型 ------- */
int lSlot = slotMap.get((IRVirtualRegister) ins.operands().get(0)); // 左操作数槽位
int rSlot = slotMap.get((IRVirtualRegister) ins.operands().get(1)); // 右操作数槽位
int dSlot = slotMap.get(ins.dest()); // 目标槽位
char lType = out.getSlotType(lSlot); // 左操作数类型前缀
char rType = out.getSlotType(rSlot); // 右操作数类型前缀
// 类型提升确定本次二元运算的目标类型优先级较高的那一个
char tType = promote(lType, rType);
String tPref = str(tType); // 用于拼接指令字符串
/* ------- 2. 加载左操作数,并自动进行类型转换(如有必要) ------- */
out.emit(OpHelper.opcode(str(lType) + "_LOAD") + " " + lSlot); // LOAD指令
String cvt = convert(lType, tType); // 如需类型提升
if (cvt != null) out.emit(OpHelper.opcode(cvt)); // 插入类型转换指令
/* ------- 3. 加载右操作数,并自动进行类型转换(如有必要) ------- */
out.emit(OpHelper.opcode(str(rType) + "_LOAD") + " " + rSlot); // LOAD指令
cvt = convert(rType, tType); // 如需类型提升
if (cvt != null) out.emit(OpHelper.opcode(cvt)); // 插入类型转换指令
/* ------- 4. 生成具体的二元运算指令 ------- */
// 获取IR指令中的操作名如ADDSUBMUL等去掉结尾的"_"后缀
String opName = ins.op().name().split("_")[0];
// 例如生成 "I_ADD", "D_MUL" 等虚拟机指令
out.emit(OpHelper.opcode(tPref + "_" + opName));
/* ------- 5. 结果存入目标槽位,并更新槽位类型 ------- */
out.emit(OpHelper.opcode(tPref + "_STORE") + " " + dSlot);
out.setSlotType(dSlot, tType); // 记录运算结果的类型前缀便于后续指令正确处理
}
}

View File

@ -0,0 +1,76 @@
package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.util.OpHelper;
import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 函数调用指令生成器
* <p>
* 该类实现了函数调用CallInstruction的指令翻译逻辑
* 负责将 IR 层的函数调用转换为虚拟机可执行的低级指令
*/
public class CallGenerator implements InstructionGenerator<CallInstruction> {
/**
* 返回本指令生成器支持的 IR 指令类型CallInstruction
*
* @return 指令类型的 Class 对象
*/
@Override
public Class<CallInstruction> supportedClass() {
return CallInstruction.class;
}
/**
* 生成函数调用相关的虚拟机指令
* <p>
* 步骤如下
* <ol>
* <li>预测返回值类型采用首个实参槽的类型作为近似</li>
* <li>为每个参数根据实际类型发出加载指令</li>
* <li>生成 CALL 调用指令</li>
* <li>将返回值存储到目标槽并记录类型信息</li>
* </ol>
*
* @param ins 待翻译的 CallInstruction 指令对象
* @param out 指令输出与类型槽管理器
* @param slotMap IR 寄存器到槽号的映射
* @param currentFn 当前函数名未用可用于递归/闭包等复杂场景
*/
@Override
public void generate(CallInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 1. 预测返回值类型用首个实参槽类型作为近似推断
char retType = 'I'; // 默认整型
if (!ins.getArguments().isEmpty()) {
int firstSlot = slotMap.get((IRVirtualRegister) ins.getArguments().getFirst());
retType = out.getSlotType(firstSlot); // 获取槽位实际类型
if (retType == '\0') retType = 'I'; // 默认整型
}
// 2. 按真实类型加载每个参数到虚拟机操作栈
for (var arg : ins.getArguments()) {
int slotId = slotMap.get((IRVirtualRegister) arg); // 获取参数槽号
char t = out.getSlotType(slotId); // 获取参数类型
if (t == '\0') t = 'I'; // 类型未知时默认整型
// 生成类型相关的加载指令 I_LOADF_LOAD
out.emit(OpHelper.opcode(String.valueOf(t) + "_LOAD") + " " + slotId);
}
// 3. 生成 CALL 调用指令
out.emitCall(ins.getFunctionName(), ins.getArguments().size());
// 4. 将返回值存入目标槽并记录槽的类型
int destSlot = slotMap.get(ins.getDest()); // 目标寄存器槽
out.emit(OpHelper.opcode(String.valueOf(retType) + "_STORE") + " " + destSlot);
out.setSlotType(destSlot, retType); // 标记返回值类型
}
}

View File

@ -0,0 +1,57 @@
package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.util.IROpCodeMapper;
import org.jcnc.snow.compiler.backend.util.OpHelper;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.ir.instruction.IRCompareJumpInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 条件比较跳转指令生成器
* <p>
* 该类实现了 {@link InstructionGenerator} 接口用于将 IR 中的条件比较跳转指令
* 转换为虚拟机可执行的指令序列主要流程是先将比较操作数加载到虚拟机栈中生成比较操作码
* 并发出跳转到目标标签的指令
*/
public class CmpJumpGenerator implements InstructionGenerator<IRCompareJumpInstruction> {
/**
* 返回该生成器所支持的指令类型
*
* @return {@link IRCompareJumpInstruction} 的类对象
*/
@Override
public Class<IRCompareJumpInstruction> supportedClass() {
return IRCompareJumpInstruction.class;
}
/**
* 生成条件比较跳转相关的虚拟机指令
*
* @param ins 需要生成的条件比较跳转中间指令IR
* @param out 虚拟机程序构建器用于输出生成的指令
* @param slotMap 虚拟寄存器到实际槽slot编号的映射表
* @param currentFn 当前处理的函数名可用于调试或作用域标识
*/
@Override
public void generate(IRCompareJumpInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 获取左操作数所在的寄存器槽编号
int leftSlot = slotMap.get(ins.left());
// 获取右操作数所在的寄存器槽编号
int rightSlot = slotMap.get(ins.right());
// 加载左操作数到虚拟机栈
out.emit(OpHelper.opcode("I_LOAD") + " " + leftSlot);
// 加载右操作数到虚拟机栈
out.emit(OpHelper.opcode("I_LOAD") + " " + rightSlot);
// 获取与当前比较操作对应的虚拟机操作码
String cmpOp = IROpCodeMapper.toVMOp(ins.op());
// 生成分支跳转指令如果比较成立则跳转到目标标签
out.emitBranch(OpHelper.opcode(cmpOp), ins.label());
}
}

View File

@ -0,0 +1,46 @@
package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.util.OpHelper;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.ir.instruction.IRJumpInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 无条件跳转指令生成器
* <p>
* 该类实现了 {@link InstructionGenerator} 接口用于将 IR 中的无条件跳转指令
* 即跳转到指定标签翻译为虚拟机指令
* </p>
*/
public class JumpGenerator implements InstructionGenerator<IRJumpInstruction> {
/**
* 返回本生成器所支持的 IR 指令类型
*
* @return {@link IRJumpInstruction} 的类对象
*/
@Override
public Class<IRJumpInstruction> supportedClass() {
return IRJumpInstruction.class;
}
/**
* 生成对应的虚拟机跳转指令
*
* @param ins 当前 IR 跳转指令
* @param out 虚拟机程序构建器用于输出指令
* @param slotMap 虚拟寄存器与槽号的映射表本跳转指令未用到
* @param currentFn 当前函数名称便于调试或作用域标识
*/
@Override
public void generate(IRJumpInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 生成无条件跳转到指定标签的虚拟机指令
out.emitBranch(OpHelper.opcode("JUMP"), ins.label());
}
}

View File

@ -0,0 +1,45 @@
package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.ir.instruction.IRLabelInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 标签指令生成器
* <p>
* 本类实现了 {@link InstructionGenerator} 接口用于将 IR 层的标签指令翻译为
* 虚拟机VM中的标签标记标签一般用于跳转或分支目的地的定位
* </p>
*/
public class LabelGenerator implements InstructionGenerator<IRLabelInstruction> {
/**
* 返回本生成器所支持的 IR 指令类型
*
* @return {@link IRLabelInstruction} 的类对象
*/
@Override
public Class<IRLabelInstruction> supportedClass() {
return IRLabelInstruction.class;
}
/**
* 生成对应的虚拟机标签指令
*
* @param ins 当前 IR 标签指令
* @param out 虚拟机程序构建器用于输出指令
* @param slotMap 虚拟寄存器与槽号的映射表标签指令未使用此参数
* @param currentFn 当前函数名称用于调试或作用域标识可选
*/
@Override
public void generate(IRLabelInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 生成标签 "label_name:"用于虚拟机指令流中的位置标记
out.emit(ins.name() + ":");
}
}

View File

@ -0,0 +1,74 @@
package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.util.OpHelper;
import org.jcnc.snow.compiler.ir.instruction.LoadConstInstruction;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 常量加载指令生成器
* 该类用于生成将常量加载到虚拟机寄存器的指令包括 PUSH 常量值和 STORE 到指定槽位
* 并为每个槽位设置正确的类型前缀 'I', 'L', 'F'
*/
public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruction> {
/**
* 返回本生成器支持的指令类型 LoadConstInstruction
*
* @return 支持的指令类型的 Class 对象
*/
@Override
public Class<LoadConstInstruction> supportedClass() {
return LoadConstInstruction.class;
}
/**
* 生成一条常量加载指令的目标虚拟机代码
*
* @param ins 当前要生成的 LoadConstInstruction 指令
* @param out VMProgramBuilder用于输出生成的虚拟机指令
* @param slotMap IR 虚拟寄存器到实际槽位编号的映射表
* @param currentFn 当前函数名如有需要可使用
*/
@Override
public void generate(LoadConstInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 1. 获取常量值第一个操作数必为常量
IRConstant constant = (IRConstant) ins.operands().getFirst();
Object value = constant.value();
// 2. 生成 PUSH 指令将常量值推入操作数栈
// 通过 OpHelper 辅助方法获取合适的数据类型前缀
String pushOp = OpHelper.pushOpcodeFor(value);
out.emit(pushOp + " " + value);
// 3. 生成 STORE 指令将栈顶的值存入对应槽位寄存器
// 同样通过 OpHelper 获取对应类型的 STORE 指令
String storeOp = OpHelper.storeOpcodeFor(value);
// 获取目标虚拟寄存器对应的槽位编号
int slot = slotMap.get(ins.dest());
out.emit(storeOp + " " + slot);
// 4. 根据常量的 Java 类型为槽位设置正确的前缀字符
// 这样在后续类型检查/运行时可用常见前缀如 'I', 'L', 'F', 'D', 'S', 'B'
char prefix = switch (value) {
case Integer _ -> 'I'; // 整型
case Long _ -> 'L'; // 长整型
case Short _ -> 'S'; // 短整型
case Byte _ -> 'B'; // 字节型
case Double _ -> 'D'; // 双精度浮点型
case Float _ -> 'F'; // 单精度浮点型
case null, default ->
throw new IllegalStateException("Unknown const type: " + (value != null ? value.getClass() : null));
};
// 写入槽位类型映射表
out.setSlotType(slot, prefix);
}
}

View File

@ -0,0 +1,55 @@
package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.util.OpHelper;
import org.jcnc.snow.compiler.ir.instruction.ReturnInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 返回指令生成器
* <p>
* 本类实现了 {@link InstructionGenerator} 接口用于将 IR 中的函数返回指令翻译为
* 虚拟机可执行的返回RET/HALT相关指令支持有返回值和无返回值两种情况
* </p>
*/
public class ReturnGenerator implements InstructionGenerator<ReturnInstruction> {
/**
* 返回该生成器支持的 IR 指令类型
*
* @return {@link ReturnInstruction} 的类对象
*/
@Override
public Class<ReturnInstruction> supportedClass() {
return ReturnInstruction.class;
}
/**
* 生成对应的虚拟机返回指令
*
* @param ins 当前 IR 返回指令
* @param out 虚拟机程序构建器用于输出 VM 指令
* @param slotMap 虚拟寄存器到槽号的映射表
* @param currentFn 当前函数名称用于判断是否为主函数 main
*/
@Override
public void generate(ReturnInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 若存在返回值先将返回值加载到虚拟机栈顶
if (ins.value() != null) {
int slotId = slotMap.get(ins.value());
// 根据之前记录的槽类型前缀I/L/S/B/D/F LOAD
char prefix = out.getSlotType(slotId);
String loadOp = prefix + "_LOAD";
out.emit(OpHelper.opcode(loadOp) + " " + slotId);
}
// 主函数返回使用 HALT普通函数返回使用 RET
String code = "main".equals(currentFn) ? "HALT" : "RET";
out.emit(OpHelper.opcode(code));
}
}

View File

@ -0,0 +1,53 @@
package org.jcnc.snow.compiler.backend.generator;
import org.jcnc.snow.compiler.backend.util.IROpCodeMapper;
import org.jcnc.snow.compiler.backend.util.OpHelper;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.ir.instruction.UnaryOperationInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 一元运算指令生成器
* <p>
* 本类实现了 {@link InstructionGenerator} 接口用于将 IR 中的一元运算指令
* 如取负等翻译为虚拟机VM可执行的指令序列
* </p>
*/
public class UnaryOpGenerator implements InstructionGenerator<UnaryOperationInstruction> {
/**
* 返回本生成器所支持的 IR 指令类型
*
* @return {@link UnaryOperationInstruction} 的类对象
*/
@Override
public Class<UnaryOperationInstruction> supportedClass() {
return UnaryOperationInstruction.class;
}
/**
* 生成一元运算相关的虚拟机指令
*
* @param ins 当前 IR 一元运算指令
* @param out 虚拟机程序构建器用于输出指令
* @param slotMap 虚拟寄存器与槽号的映射表
* @param currentFn 当前函数名称用于作用域或调试可选
*/
@Override
public void generate(UnaryOperationInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 获取操作数所在槽号
int slotId = slotMap.get((IRVirtualRegister) ins.operands().getFirst());
// 加载操作数到虚拟机栈顶
out.emit(OpHelper.opcode("I_LOAD") + " " + slotId);
// 生成对应的一元运算操作码如取负等
out.emit(OpHelper.opcode(IROpCodeMapper.toVMOp(ins.op())));
// 将结果存储到目标寄存器槽
out.emit(OpHelper.opcode("I_STORE") + " " + slotMap.get(ins.dest()));
}
}

View File

@ -0,0 +1,127 @@
package org.jcnc.snow.compiler.backend.util;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import java.util.EnumMap;
import java.util.Map;
/**
* IR 操作码与虚拟机指令名映射工具类
* <p>
* 本类用于将 IR 层的操作码{@link IROpCode}映射为目标虚拟机的指令名{@code String}
* 该工具类在编译器后端阶段提供指令名转换功能不处理参数或操作数仅负责纯映射
* </p>
* <p>
* 若需扩展新的操作码或 VM 指令应在本类中统一维护映射关系
* </p>
*/
public final class IROpCodeMapper {
/**
* IR 操作码到 VM 指令名的静态映射表
* <ul>
* <li>IR 操作码{@link IROpCode} 枚举项</li>
* <li>对应虚拟机指令名字符串</li>
* </ul>
* 使用 {@link EnumMap}查找和存储高效
*/
private static final Map<IROpCode, String> opcodeMap = new EnumMap<>(IROpCode.class);
// 静态代码块初始化所有 IR 操作码到 VM 指令名的映射关系
static {
opcodeMap.put(IROpCode.CONV_I32_TO_F32, "I2F");
opcodeMap.put(IROpCode.CONV_I32_TO_D64, "I2D");
opcodeMap.put(IROpCode.CONV_F32_TO_I32, "F2I");
opcodeMap.put(IROpCode.CONV_D64_TO_I32, "D2I");
opcodeMap.put(IROpCode.CONV_F32_TO_D64, "F2D");
opcodeMap.put(IROpCode.CONV_D64_TO_F32, "D2F");
// 整形8位算术运算映射
opcodeMap.put(IROpCode.ADD_B8, "B_ADD");
opcodeMap.put(IROpCode.SUB_B8, "B_SUB");
opcodeMap.put(IROpCode.MUL_B8, "B_MUL");
opcodeMap.put(IROpCode.DIV_B8, "B_DIV");
opcodeMap.put(IROpCode.NEG_B8, "B_NEG");
// 整形16位算术运算映射
opcodeMap.put(IROpCode.ADD_S16, "S_ADD");
opcodeMap.put(IROpCode.SUB_S16, "S_SUB");
opcodeMap.put(IROpCode.MUL_S16, "S_MUL");
opcodeMap.put(IROpCode.DIV_S16, "S_DIV");
opcodeMap.put(IROpCode.NEG_S16, "S_NEG");
// 整形32位算术运算映射
opcodeMap.put(IROpCode.ADD_I32, "I_ADD");
opcodeMap.put(IROpCode.SUB_I32, "I_SUB");
opcodeMap.put(IROpCode.MUL_I32, "I_MUL");
opcodeMap.put(IROpCode.DIV_I32, "I_DIV");
opcodeMap.put(IROpCode.NEG_I32, "I_NEG");
// 整形64位算术运算映射
opcodeMap.put(IROpCode.ADD_L64, "L_ADD");
opcodeMap.put(IROpCode.SUB_L64, "L_SUB");
opcodeMap.put(IROpCode.MUL_L64, "L_MUL");
opcodeMap.put(IROpCode.DIV_L64, "L_DIV");
opcodeMap.put(IROpCode.NEG_L64, "L_NEG");
// --- 32-bit floating point ---
opcodeMap.put(IROpCode.ADD_F32, "F_ADD");
opcodeMap.put(IROpCode.SUB_F32, "F_SUB");
opcodeMap.put(IROpCode.MUL_F32, "F_MUL");
opcodeMap.put(IROpCode.DIV_F32, "F_DIV");
opcodeMap.put(IROpCode.NEG_F32, "F_NEG");
// --- 64-bit floating point ---
opcodeMap.put(IROpCode.ADD_D64, "D_ADD");
opcodeMap.put(IROpCode.SUB_D64, "D_SUB");
opcodeMap.put(IROpCode.MUL_D64, "D_MUL");
opcodeMap.put(IROpCode.DIV_D64, "D_DIV");
opcodeMap.put(IROpCode.NEG_D64, "D_NEG");
// 比较运算映射
opcodeMap.put(IROpCode.CMP_EQ, "IC_EQ"); // 相等
opcodeMap.put(IROpCode.CMP_NE, "IC_NE"); // 不等
opcodeMap.put(IROpCode.CMP_LT, "IC_L"); // 小于
opcodeMap.put(IROpCode.CMP_GT, "IC_G"); // 大于
opcodeMap.put(IROpCode.CMP_LE, "IC_LE"); // 小于等于
opcodeMap.put(IROpCode.CMP_GE, "IC_GE"); // 大于等于
// 加载与存储
opcodeMap.put(IROpCode.LOAD, "I_LOAD"); // 加载
opcodeMap.put(IROpCode.STORE, "I_STORE"); // 存储
opcodeMap.put(IROpCode.CONST, "I_PUSH"); // 常量入栈
// 跳转与标签
opcodeMap.put(IROpCode.JUMP, "JMP"); // 无条件跳转
opcodeMap.put(IROpCode.LABEL, "LABEL"); // 标签
// 函数相关
opcodeMap.put(IROpCode.CALL, "CALL"); // 调用
opcodeMap.put(IROpCode.RET, "RET"); // 返回
}
/**
* 工具类私有构造禁止实例化
*/
private IROpCodeMapper() {
// 禁止实例化
}
/**
* 根据指定 IR 操作码获取对应的虚拟机指令名
*
* @param irOp 需转换的 IR 操作码{@link IROpCode} 枚举值
* @return 对应的虚拟机指令名字符串
* @throws IllegalArgumentException {@code irOp} 未定义映射关系抛出异常
*/
public static String toVMOp(IROpCode irOp) {
String vmCode = opcodeMap.get(irOp);
if (vmCode == null) {
throw new IllegalArgumentException("未映射的 IR 操作码: " + irOp);
}
return vmCode;
}
}

View File

@ -0,0 +1,168 @@
package org.jcnc.snow.compiler.backend.util;
import org.jcnc.snow.vm.engine.VMOpCode;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Opcode 帮助类
* <p>
* 通过 <strong>静态不可变 Map</strong> 保存指令名到 opcode以字符串表示的映射表
* <p>
* 本文件由脚本根据 {@link VMOpCode} 中实际存在的 <code>public static</code> 字段自动生成
* 保证与指令集保持同步避免手写出错
* </p>
*/
public final class OpHelper {
/**
* 指令名 opcode 字符串 的静态映射表
*/
private static final Map<String, String> OPCODE_MAP;
static {
Map<String, String> map = new HashMap<>();
map.put("I_ADD", Integer.toString(VMOpCode.I_ADD));
map.put("I_SUB", Integer.toString(VMOpCode.I_SUB));
map.put("I_MUL", Integer.toString(VMOpCode.I_MUL));
map.put("I_DIV", Integer.toString(VMOpCode.I_DIV));
map.put("I_MOD", Integer.toString(VMOpCode.I_MOD));
map.put("I_INC", Integer.toString(VMOpCode.I_INC));
map.put("I_NEG", Integer.toString(VMOpCode.I_NEG));
map.put("L_ADD", Integer.toString(VMOpCode.L_ADD));
map.put("L_SUB", Integer.toString(VMOpCode.L_SUB));
map.put("L_MUL", Integer.toString(VMOpCode.L_MUL));
map.put("L_DIV", Integer.toString(VMOpCode.L_DIV));
map.put("L_MOD", Integer.toString(VMOpCode.L_MOD));
map.put("L_INC", Integer.toString(VMOpCode.L_INC));
map.put("L_NEG", Integer.toString(VMOpCode.L_NEG));
map.put("S_ADD", Integer.toString(VMOpCode.S_ADD));
map.put("S_SUB", Integer.toString(VMOpCode.S_SUB));
map.put("S_MUL", Integer.toString(VMOpCode.S_MUL));
map.put("S_DIV", Integer.toString(VMOpCode.S_DIV));
map.put("S_MOD", Integer.toString(VMOpCode.S_MOD));
map.put("S_INC", Integer.toString(VMOpCode.S_INC));
map.put("S_NEG", Integer.toString(VMOpCode.S_NEG));
map.put("B_ADD", Integer.toString(VMOpCode.B_ADD));
map.put("B_SUB", Integer.toString(VMOpCode.B_SUB));
map.put("B_MUL", Integer.toString(VMOpCode.B_MUL));
map.put("B_DIV", Integer.toString(VMOpCode.B_DIV));
map.put("B_MOD", Integer.toString(VMOpCode.B_MOD));
map.put("B_INC", Integer.toString(VMOpCode.B_INC));
map.put("B_NEG", Integer.toString(VMOpCode.B_NEG));
map.put("D_ADD", Integer.toString(VMOpCode.D_ADD));
map.put("D_SUB", Integer.toString(VMOpCode.D_SUB));
map.put("D_MUL", Integer.toString(VMOpCode.D_MUL));
map.put("D_DIV", Integer.toString(VMOpCode.D_DIV));
map.put("D_MOD", Integer.toString(VMOpCode.D_MOD));
map.put("D_NEG", Integer.toString(VMOpCode.D_NEG));
map.put("F_ADD", Integer.toString(VMOpCode.F_ADD));
map.put("F_SUB", Integer.toString(VMOpCode.F_SUB));
map.put("F_MUL", Integer.toString(VMOpCode.F_MUL));
map.put("F_DIV", Integer.toString(VMOpCode.F_DIV));
map.put("F_MOD", Integer.toString(VMOpCode.F_MOD));
map.put("F_NEG", Integer.toString(VMOpCode.F_NEG));
map.put("D_INC", Integer.toString(VMOpCode.D_INC));
map.put("F_INC", Integer.toString(VMOpCode.F_INC));
map.put("I2L", Integer.toString(VMOpCode.I2L));
map.put("I2S", Integer.toString(VMOpCode.I2S));
map.put("I2B", Integer.toString(VMOpCode.I2B));
map.put("I2D", Integer.toString(VMOpCode.I2D));
map.put("I2F", Integer.toString(VMOpCode.I2F));
map.put("L2I", Integer.toString(VMOpCode.L2I));
map.put("L2D", Integer.toString(VMOpCode.L2D));
map.put("L2F", Integer.toString(VMOpCode.L2F));
map.put("F2I", Integer.toString(VMOpCode.F2I));
map.put("F2L", Integer.toString(VMOpCode.F2L));
map.put("F2D", Integer.toString(VMOpCode.F2D));
map.put("D2I", Integer.toString(VMOpCode.D2I));
map.put("D2L", Integer.toString(VMOpCode.D2L));
map.put("D2F", Integer.toString(VMOpCode.D2F));
map.put("S2I", Integer.toString(VMOpCode.S2I));
map.put("B2I", Integer.toString(VMOpCode.B2I));
map.put("I_AND", Integer.toString(VMOpCode.I_AND));
map.put("I_OR", Integer.toString(VMOpCode.I_OR));
map.put("I_XOR", Integer.toString(VMOpCode.I_XOR));
map.put("L_AND", Integer.toString(VMOpCode.L_AND));
map.put("L_OR", Integer.toString(VMOpCode.L_OR));
map.put("L_XOR", Integer.toString(VMOpCode.L_XOR));
map.put("JUMP", Integer.toString(VMOpCode.JUMP));
map.put("IC_E", Integer.toString(VMOpCode.IC_E));
map.put("IC_NE", Integer.toString(VMOpCode.IC_NE));
map.put("IC_G", Integer.toString(VMOpCode.IC_G));
map.put("IC_GE", Integer.toString(VMOpCode.IC_GE));
map.put("IC_L", Integer.toString(VMOpCode.IC_L));
map.put("IC_LE", Integer.toString(VMOpCode.IC_LE));
map.put("I_PUSH", Integer.toString(VMOpCode.I_PUSH));
map.put("L_PUSH", Integer.toString(VMOpCode.L_PUSH));
map.put("S_PUSH", Integer.toString(VMOpCode.S_PUSH));
map.put("B_PUSH", Integer.toString(VMOpCode.B_PUSH));
map.put("D_PUSH", Integer.toString(VMOpCode.D_PUSH));
map.put("F_PUSH", Integer.toString(VMOpCode.F_PUSH));
map.put("POP", Integer.toString(VMOpCode.POP));
map.put("DUP", Integer.toString(VMOpCode.DUP));
map.put("SWAP", Integer.toString(VMOpCode.SWAP));
map.put("I_STORE", Integer.toString(VMOpCode.I_STORE));
map.put("L_STORE", Integer.toString(VMOpCode.L_STORE));
map.put("S_STORE", Integer.toString(VMOpCode.S_STORE));
map.put("B_STORE", Integer.toString(VMOpCode.B_STORE));
map.put("D_STORE", Integer.toString(VMOpCode.D_STORE));
map.put("F_STORE", Integer.toString(VMOpCode.F_STORE));
map.put("I_LOAD", Integer.toString(VMOpCode.I_LOAD));
map.put("L_LOAD", Integer.toString(VMOpCode.L_LOAD));
map.put("S_LOAD", Integer.toString(VMOpCode.S_LOAD));
map.put("B_LOAD", Integer.toString(VMOpCode.B_LOAD));
map.put("D_LOAD", Integer.toString(VMOpCode.D_LOAD));
map.put("F_LOAD", Integer.toString(VMOpCode.F_LOAD));
map.put("MOV", Integer.toString(VMOpCode.MOV));
map.put("CALL", Integer.toString(VMOpCode.CALL));
map.put("RET", Integer.toString(VMOpCode.RET));
map.put("HALT", Integer.toString(VMOpCode.HALT));
OPCODE_MAP = Collections.unmodifiableMap(map);
}
/**
* 私有构造器禁止实例化
*/
private OpHelper() {
}
/**
* 根据指令名获取 opcode 字符串
*
* @param name 指令名 "I_LOAD""I_PUSH"
* @return opcode 字符串
* @throws IllegalStateException 若名称未知
*/
public static String opcode(String name) {
String code = OPCODE_MAP.get(name);
if (code == null) {
throw new IllegalStateException("Unknown opcode: " + name);
}
return code;
}
// region 辅助方法 根据常量类型推导 PUSH / STORE 指令名
public static String pushOpcodeFor(Object v) {
return opcode(typePrefix(v) + "_PUSH");
}
public static String storeOpcodeFor(Object v) {
return opcode(typePrefix(v) + "_STORE");
}
private static String typePrefix(Object v) {
if (v instanceof Integer) return "I";
if (v instanceof Long) return "L";
if (v instanceof Short) return "S";
if (v instanceof Byte) return "B";
if (v instanceof Double) return "D";
if (v instanceof Float) return "F";
throw new IllegalStateException("Unknown const type: " + v.getClass());
}
// endregion
}

View File

@ -0,0 +1,149 @@
package org.jcnc.snow.compiler.cli;
import org.jcnc.snow.compiler.backend.alloc.RegisterAllocator;
import org.jcnc.snow.compiler.backend.builder.VMCodeGenerator;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.generator.*;
import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IRProgram;
import org.jcnc.snow.compiler.lexer.core.LexerEngine;
import org.jcnc.snow.compiler.parser.ast.base.Node;
import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.core.ParserEngine;
import org.jcnc.snow.compiler.parser.function.ASTPrinter;
import org.jcnc.snow.compiler.semantic.core.SemanticAnalyzerRunner;
import org.jcnc.snow.vm.engine.VMMode;
import org.jcnc.snow.vm.engine.VirtualMachineEngine;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
/**
* SnowCompiler CLI 多文件 / 单文件 / 目录 模式
*/
public class SnowCompiler {
public static void main(String[] args) throws IOException {
if (args.length == 0) {
System.err.println("""
Usage:
snow <file1.snow> [file2.snow ]
snow -d <srcDir> (compile all *.snow recursively)
""");
return;
}
/* ---------- 1. 收集所有待编译源码 ---------- */
List<Path> sources = collectSources(args);
if (sources.isEmpty()) {
System.err.println("No .snow source files found.");
return;
}
/* ---------- 2. 逐个词法+语法分析,合并 AST ---------- */
List<Node> allAst = new ArrayList<>();
for (Path p : sources) {
if (!Files.exists(p)) {
System.err.println("File not found: " + p);
return;
}
String code = Files.readString(p, StandardCharsets.UTF_8);
// 保持原有## 源代码打印但标注文件名兼容旧脚本
System.out.println("## 源代码 (" + p.getFileName() + ")");
System.out.println(code);
LexerEngine lexer = new LexerEngine(code, p.toString());
ParserContext ctx = new ParserContext(lexer.getAllTokens());
allAst.addAll(new ParserEngine(ctx).parse());
}
/* ---------- 3. 语义分析 ---------- */
SemanticAnalyzerRunner.runSemanticAnalysis(allAst, false);
/* ---------- 4. AST → IR ---------- */
IRProgram program = new IRProgramBuilder().buildProgram(allAst);
program = reorderForEntry(program); // 保证入口 main 在首位
System.out.println("## 编译器输出");
System.out.println("### AST");
ASTPrinter.printJson(allAst);
System.out.println("### IR");
System.out.println(program);
/* ---------- 5. IR → VM 指令 ---------- */
VMProgramBuilder builder = new VMProgramBuilder();
List<InstructionGenerator<? extends IRInstruction>> generators = Arrays.asList(
new LoadConstGenerator(),
new BinaryOpGenerator(),
new UnaryOpGenerator(),
new CallGenerator(),
new ReturnGenerator(),
new LabelGenerator(),
new JumpGenerator(),
new CmpJumpGenerator()
);
for (IRFunction fn : program.functions()) {
Map<org.jcnc.snow.compiler.ir.value.IRVirtualRegister, Integer> slotMap =
new RegisterAllocator().allocate(fn);
new VMCodeGenerator(slotMap, builder, generators).generate(fn);
}
List<String> finalCode = builder.build();
System.out.println("### VM code");
finalCode.forEach(System.out::println);
/* ---------- 6. 运行虚拟机 ---------- */
VirtualMachineEngine vm = new VirtualMachineEngine(VMMode.RUN);
vm.execute(finalCode);
vm.printLocalVariables();
}
/**
* 根据参数收集待编译文件
* - snow file1 file2 多文件 / 单文件
* - snow -d srcDir 目录递归所有 *.snow
*/
private static List<Path> collectSources(String[] args) throws IOException {
if (args.length == 2 && "-d".equals(args[0])) {
Path dir = Path.of(args[1]);
if (!Files.isDirectory(dir)) {
System.err.println("Not a directory: " + dir);
return List.of();
}
try (var stream = Files.walk(dir)) {
return stream.filter(p -> p.toString().endsWith(".snow"))
.sorted() // 稳定顺序方便比对输出
.toList();
}
}
// 普通文件参数
return Arrays.stream(args).map(Path::of).toList();
}
/**
* main 函数放到 Program.functions()[0]保证 PC=0 即入口
* 如果用户未写 main则保持原顺序语义分析会报错
*/
private static IRProgram reorderForEntry(IRProgram in) {
List<IRFunction> ordered = new ArrayList<>(in.functions());
int idx = -1;
for (int i = 0; i < ordered.size(); i++) {
if ("main".equals(ordered.get(i).name())) {
idx = i; break;
}
}
if (idx > 0) Collections.swap(ordered, 0, idx);
IRProgram out = new IRProgram();
ordered.forEach(out::add);
return out;
}
}

View File

@ -0,0 +1,182 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
import org.jcnc.snow.compiler.ir.instruction.LoadConstInstruction;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
import org.jcnc.snow.compiler.parser.ast.*;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import java.util.*;
/**
* <b>表达式构建器</b>
* <p>
* 该类负责将抽象语法树AST的表达式节点转换为中间表示IR指令和虚拟寄存器
* 是编译器IR生成阶段的核心工具
* <br/>
* 主要职责包括
* <ul>
* <li>将数字字面量标识符二元表达式函数调用等AST表达式节点翻译为对应的IR指令序列</li>
* <li>管理并分配虚拟寄存器保证IR操作的数据流正确</li>
* </ul>
* <p>
*/
public record ExpressionBuilder(IRContext ctx) {
/**
* 构建并返回某个表达式节点对应的虚拟寄存器
*
* <p>会根据节点的实际类型分别处理
* <ul>
* <li>数字字面量新建常量寄存器</li>
* <li>标识符查找当前作用域中的寄存器</li>
* <li>二元表达式递归处理子表达式并进行相应运算</li>
* <li>函数调用生成对应的Call指令</li>
* <li>其它类型不支持抛出异常</li>
* </ul>
*
* @param expr 要转换的表达式AST节点
* @return 该表达式的计算结果寄存器
* @throws IllegalStateException 如果遇到未定义的标识符或不支持的表达式类型
*/
public IRVirtualRegister build(ExpressionNode expr) {
return switch (expr) {
// 数字字面量 "123", "1.0f"
case NumberLiteralNode n -> buildNumberLiteral(n.value());
// 标识符如变量x直接查作用域
case IdentifierNode id -> {
IRVirtualRegister reg = ctx.getScope().lookup(id.name());
if (reg == null) throw new IllegalStateException("未定义标识符: " + id.name());
yield reg;
}
// 二元表达式 "a + b"
case BinaryExpressionNode bin -> buildBinary(bin);
// 函数调用 foo(a, b)
case CallExpressionNode call -> buildCall(call);
// 其它不支持
default -> throw new IllegalStateException("不支持的表达式类型: " + expr.getClass().getSimpleName());
};
}
/**
* 直接将表达式计算结果写入指定的目标寄存器dest
* <p>
* {@link #build(ExpressionNode)}类似但支持目标寄存器复用避免不必要的move
*
* @param node 表达式AST节点
* @param dest 目标寄存器
* @throws IllegalStateException 未定义标识符/不支持的表达式类型时报错
*/
public void buildInto(ExpressionNode node, IRVirtualRegister dest) {
switch (node) {
// 数字字面量直接加载到目标寄存器
case NumberLiteralNode n ->
InstructionFactory.loadConstInto(ctx, dest, ExpressionUtils.parseIntSafely(n.value()));
// 标识符查找并move到目标寄存器
case IdentifierNode id -> {
IRVirtualRegister src = ctx.getScope().lookup(id.name());
if (src == null) throw new IllegalStateException("未定义标识符: " + id.name());
InstructionFactory.move(ctx, src, dest);
}
// 二元表达式直接写入目标寄存器
case BinaryExpressionNode bin -> buildBinaryInto(bin, dest);
// 其他表达式先递归生成寄存器再move到目标寄存器
default -> {
IRVirtualRegister tmp = build(node);
InstructionFactory.move(ctx, tmp, dest);
}
}
}
// ===================== 内部私有方法 =====================
/**
* 构建二元表达式的IR生成新寄存器存储结果
* <p>
* 先递归构建左右操作数之后根据操作符类别算术或比较决定生成的IR操作码
* 并生成对应的二元运算指令
*
* @param bin 二元表达式节点
* @return 存放结果的虚拟寄存器
*/
private IRVirtualRegister buildBinary(BinaryExpressionNode bin) {
String op = bin.operator();
IRVirtualRegister left = build(bin.left());
IRVirtualRegister right = build(bin.right());
// 处理比较操作符
if (ExpressionUtils.isComparisonOperator(op)) {
return InstructionFactory.binOp(ctx, ExpressionUtils.cmpOp(op), left, right);
}
// 处理算术运算符
IROpCode code = ExpressionUtils.resolveOpCode(op, bin.left(), bin.right());
if (code == null) throw new IllegalStateException("不支持的运算符: " + op);
return InstructionFactory.binOp(ctx, code, left, right);
}
/**
* 将二元表达式的结果直接写入指定寄存器dest
* <p>
* 结构与{@link #buildBinary(BinaryExpressionNode)}类似但不会新分配寄存器
*
* @param bin 二元表达式节点
* @param dest 目标寄存器
*/
private void buildBinaryInto(BinaryExpressionNode bin, IRVirtualRegister dest) {
IRVirtualRegister a = build(bin.left());
IRVirtualRegister b = build(bin.right());
String op = bin.operator();
if (ExpressionUtils.isComparisonOperator(op)) {
InstructionFactory.binOpInto(ctx, ExpressionUtils.cmpOp(op), a, b, dest);
} else {
IROpCode code = ExpressionUtils.resolveOpCode(op, bin.left(), bin.right());
if (code == null) throw new IllegalStateException("不支持的运算符: " + op);
InstructionFactory.binOpInto(ctx, code, a, b, dest);
}
}
/**
* 处理函数调用表达式生成对应的Call指令和目标寄存器
* <p>
* 支持普通标识符调用和成员调用 mod.func会为每个参数依次生成子表达式的寄存器
*
* @param call 调用表达式AST节点
* @return 返回结果存放的寄存器
*/
private IRVirtualRegister buildCall(CallExpressionNode call) {
// 递归构建所有参数的寄存器
List<IRVirtualRegister> argv = call.arguments().stream()
.map(this::build)
.toList();
// 获取完整调用目标名称支持成员/模块调用和普通调用
String fullName = switch (call.callee()) {
case MemberExpressionNode member when member.object() instanceof IdentifierNode mod ->
((IdentifierNode)member.object()).name() + "." + member.member();
case IdentifierNode id -> id.name();
default -> throw new IllegalStateException("不支持的调用目标: " + call.callee().getClass().getSimpleName());
};
// 申请目标寄存器
IRVirtualRegister dest = ctx.newRegister();
// 添加Call指令到IR上下文
ctx.addInstruction(new CallInstruction(dest, fullName, new ArrayList<>(argv)));
return dest;
}
/**
* 处理数字字面量生成常量寄存器和加载指令
* <p>
* 会将字符串型字面量 "123", "1.0f"解析为具体的IRConstant
* 并分配一个新的虚拟寄存器来存放该常量
*
* @param value 字面量字符串
* @return 存放该常量的寄存器
*/
private IRVirtualRegister buildNumberLiteral(String value) {
IRConstant constant = ExpressionUtils.buildNumberConstant(value);
IRVirtualRegister reg = ctx.newRegister();
ctx.addInstruction(new LoadConstInstruction(reg, constant));
return reg;
}
}

View File

@ -0,0 +1,71 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
import org.jcnc.snow.compiler.parser.ast.ParameterNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
/**
* IR 函数构建器
* <p>
* 负责将语法树中的 FunctionNode 节点转化为可执行的 IRFunction
* 包含参数声明返回类型推断函数体语句转换等步骤
*/
public class FunctionBuilder {
/**
* AST 中的 FunctionNode 构建为可执行的 IRFunction
* <p>
* 构建过程包括
* <ol>
* <li>初始化 IRFunction 实例和上下文</li>
* <li>根据函数返回类型设置默认类型后缀便于表达式推断</li>
* <li>声明参数到作用域并为每个参数分配虚拟寄存器</li>
* <li>遍历并转换函数体内的每条语句为 IR 指令</li>
* <li>函数构建完成后清理默认类型后缀防止影响其他函数</li>
* </ol>
*
* @param functionNode 表示函数定义的语法树节点
* @return 构建得到的 IRFunction 对象
*/
public IRFunction build(FunctionNode functionNode) {
// 0) 基本初始化创建 IRFunction 实例与对应上下文
IRFunction irFunction = new IRFunction(functionNode.name());
IRContext irContext = new IRContext(irFunction);
// 1) 把函数返回类型注入为默认类型后缀供表达式类型推断
char _returnSuffix = switch (functionNode.returnType().toLowerCase()) {
case "double" -> 'd';
case "float" -> 'f';
case "long" -> 'l';
case "short" -> 's';
case "byte" -> 'b';
default -> '\0';
};
ExpressionUtils.setDefaultSuffix(_returnSuffix);
try {
// 2) 声明形参为每个参数分配虚拟寄存器并声明到作用域
for (ParameterNode p : functionNode.parameters()) {
IRVirtualRegister reg = irFunction.newRegister(); // 新寄存器
irContext.getScope().declare(p.name(), reg); // 变量名寄存器绑定
irFunction.addParameter(reg); // 添加到函数参数列表
}
// 3) 生成函数体 IR遍历每条语句逐一转化
StatementBuilder stmtBuilder = new StatementBuilder(irContext);
for (StatementNode stmt : functionNode.body()) {
stmtBuilder.build(stmt);
}
} finally {
// 4) 清除默认后缀避免影响后续函数的推断
ExpressionUtils.clearDefaultSuffix();
}
// 返回构建好的 IRFunction
return irFunction;
}
}

View File

@ -0,0 +1,85 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.HashMap;
import java.util.Map;
/**
* IRBuilderScope 用于管理单个函数内变量名与虚拟寄存器的映射关系
*
* <p>主要功能包括
* <ul>
* <li>维护在当前作用域中已声明变量的寄存器分配信息</li>
* <li>支持将已有虚拟寄存器与变量名重新绑定</li>
* <li>根据变量名查找对应的虚拟寄存器实例</li>
* </ul>
*/
final class IRBuilderScope {
/**
* 存储变量名到对应 IRVirtualRegister 的映射
* 变量名为键虚拟寄存器对象为值用于查找和更新
*/
private final Map<String, IRVirtualRegister> vars = new HashMap<>();
/**
* 当前作用域所绑定的 IRFunction 对象用于申请新的虚拟寄存器
*/
private IRFunction fn;
/**
* 将指定的 IRFunction 关联到当前作用域以便后续声明变量时能够
* 调用该函数的 newRegister() 方法生成新的寄存器
*
* @param fn 要绑定到本作用域的 IRFunction 实例
*/
void attachFunction(IRFunction fn) {
this.fn = fn;
}
/**
* 在当前作用域中声明一个新变量并为其分配一个新的虚拟寄存器
* 调用绑定的 IRFunction.newRegister() 生成寄存器后保存到映射表中
*
* @param name 变量名称作为映射键使用
*/
void declare(String name) {
IRVirtualRegister reg = fn.newRegister();
vars.put(name, reg);
}
/**
* 在当前作用域中声明或导入一个已有的虚拟寄存器并将其与指定变量名绑定
* 该方法可用于将外部或前一作用域的寄存器导入到本作用域
*
* @param name 变量名称作为映射键使用
* @param reg 要绑定到该名称的 IRVirtualRegister 实例
*/
void declare(String name, IRVirtualRegister reg) {
vars.put(name, reg);
}
/**
* 更新已存在变量的虚拟寄存器绑定关系若变量已声明则替换其对应的寄存器
* 若尚未声明则等同于声明新变量
*
* @param name 变量名称作为映射键使用
* @param reg 新的 IRVirtualRegister 实例用于替换旧绑定
*/
void put(String name, IRVirtualRegister reg) {
vars.put(name, reg);
}
/**
* 根据变量名称在当前作用域中查找对应的虚拟寄存器
*
* @param name 需要查询的变量名称
* @return 如果该名称已绑定寄存器则返回对应的 IRVirtualRegister
* 如果未声明则返回 null
*/
IRVirtualRegister lookup(String name) {
return vars.get(name);
}
}

View File

@ -0,0 +1,88 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
/**
* IRContext 类负责封装当前正在构建的 IRFunction 实例
* 以及与之配套的作用域管理IRBuilderScope
* 并简化虚拟寄存器分配与 IR 指令添加操作
*
* <p>本类提供以下核心功能
* <ul>
* <li>持有并操作当前 IRFunction 对象</li>
* <li>管理变量名与虚拟寄存器的映射关系</li>
* <li>分配新的虚拟寄存器实例</li>
* <li>将生成的 IRInstruction 自动添加到 IRFunction </li>
* </ul>
*/
public class IRContext {
/* ➡ 新增:生成唯一标签用 */
private int labelCounter = 0;
/**
* 当前正在构建的 IRFunction 对象所有指令将添加至此
*/
private final IRFunction function;
/**
* 用于管理当前函数作用域内变量与虚拟寄存器的映射
*/
private final IRBuilderScope scope;
/**
* 构造一个新的 IRContext并将指定的 IRFunction 与作用域关联
*
* @param function 要构建的 IRFunction 实例
*/
public IRContext(IRFunction function) {
this.function = function;
this.scope = new IRBuilderScope();
// 关联作用域与 IRFunction以便在声明变量时申请寄存器
this.scope.attachFunction(function);
}
/**
* 获取当前正在构建的 IRFunction 对象
*
* @return 当前 IRFunction 实例
*/
public IRFunction getFunction() {
return function;
}
/**
* 获取当前函数的变量与寄存器映射作用域
*
* <p>包内可见仅限 builder 包内部使用
*
* @return IRBuilderScope 实例
*/
IRBuilderScope getScope() {
return scope;
}
/**
* 为当前函数分配一个新的虚拟寄存器
*
* @return 分配到的 IRVirtualRegister 对象
*/
public IRVirtualRegister newRegister() {
return function.newRegister();
}
/**
* 将指定的 IRInstruction 添加到当前 IRFunction 的指令列表中
*
* @param instr 要添加的 IRInstruction 实例
*/
public void addInstruction(IRInstruction instr) {
function.add(instr);
}
/** 生成一个形如 L0 / L1 ... 的唯一标签名 */
public String newLabel() {
return "L" + (labelCounter++);
}
}

View File

@ -0,0 +1,81 @@
package org.jcnc.snow.compiler.ir.builder;
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.base.Node;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
import java.util.List;
/**
* 本类负责将解析生成的 AST 根节点列表转换为可执行的 IRProgram
*
* <p>主要职责
* <ul>
* <li>遍历输入的顶层节点识别 ModuleNodeFunctionNode 及脚本式顶层 StatementNode</li>
* <li> ModuleNode 中的所有函数节点调用 FunctionBuilder 构建 IRFunction 并添加至 IRProgram</li>
* <li>对单独的 FunctionNode 节点直接构建并纳入 IRProgram</li>
* <li>对顶层脚本式 StatementNode 自动封装为名称固定的_start函数再行构建并纳入 IRProgram</li>
* <li>对不支持的节点类型抛出 IllegalStateException以确保编译流程严谨</li>
* </ul>
*/
public final class IRProgramBuilder {
/**
* 构建完整的 IRProgram 实例
*
* @param roots ModuleNodeFunctionNode StatementNode 的顶层 AST 根节点列表
* @return 包含所有转换后 IRFunction IRProgram 对象
* @throws IllegalStateException 遇到不支持的顶层节点类型时抛出
*/
public IRProgram buildProgram(List<Node> roots) {
IRProgram irProgram = new IRProgram();
for (Node node : roots) {
switch (node) {
case ModuleNode moduleNode ->
// 模块节点批量构建并添加模块内所有函数
moduleNode.functions().forEach(f -> irProgram.add(buildFunction(f)));
case FunctionNode functionNode ->
// 顶层函数节点直接构建并添加
irProgram.add(buildFunction(functionNode));
case StatementNode statementNode ->
// 脚本式顶层语句封装为_start函数后构建并添加
irProgram.add(buildFunction(wrapTopLevel(statementNode)));
default ->
// 严格校验节点类型遇不支持者立即失败
throw new IllegalStateException("Unsupported top-level node: " + node);
}
}
return irProgram;
}
/**
* 利用 FunctionBuilder FunctionNode 转换为 IRFunction
*
* @param functionNode 待构建的 AST FunctionNode
* @return 构建完成的 IRFunction 实例
*/
private IRFunction buildFunction(FunctionNode functionNode) {
return new FunctionBuilder().build(functionNode);
}
/**
* 将单个脚本式顶层 StatementNode 封装为名称固定的_start函数节点
*
* <p>封装规则
* <ul>
* <li>函数名固定为_start</li>
* <li>返回类型设为 null由后续流程处理</li>
* <li>参数列表为空</li>
* <li>函数主体仅包含传入的单条语句</li>
* </ul>
*
* @param stmt 待封装的顶层脚本语句节点
* @return 生成的 FunctionNode用于后续 IRFunction 构建
*/
private FunctionNode wrapTopLevel(StatementNode stmt) {
return new FunctionNode("_start", null, String.valueOf(List.of()), List.of(stmt));
}
}

View File

@ -0,0 +1,155 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.instruction.*;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
/**
* InstructionFactory 统一生成并注册 IR 指令的工厂类
* <p>
* 该类封装了常见的 IR 指令生成方式包括常量加载二元运算赋值控制流等
* 统一简化指令插入和寄存器分配逻辑提升 IR 生成阶段的代码可维护性和复用性
* </p>
*/
public class InstructionFactory {
/* ====================================================================== */
/* 常量 / 通用二元运算(新寄存器) */
/* ====================================================================== */
/**
* 加载整数常量将其写入一个新分配的虚拟寄存器并返回该寄存器
*
* @param ctx 当前 IR 上下文用于分配寄存器与添加指令
* @param value 要加载的整数常量值
* @return 存储该常量的新虚拟寄存器
*/
public static IRVirtualRegister loadConst(IRContext ctx, int 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 保存运算结果的新虚拟寄存器
*/
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 要加载的整数常量
*/
public static void loadConstInto(IRContext ctx, IRVirtualRegister dest, int value) {
ctx.addInstruction(new LoadConstInstruction(dest, new IRConstant(value)));
}
/**
* 对两个寄存器执行二元运算将结果写入指定目标寄存器
*
* @param ctx 当前 IR 上下文
* @param op 运算类型IROpCode 枚举
* @param a 第一个操作数寄存器
* @param b 第二个操作数寄存器
* @param 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>
*
* @param ctx 当前 IR 上下文
* @param src 源寄存器
* @param dest 目标寄存器
*/
public static void move(IRContext ctx, IRVirtualRegister src, IRVirtualRegister dest) {
/* 采用 “dest = src + 0” 的最简实现 */
IRVirtualRegister zero = loadConst(ctx, 0);
ctx.addInstruction(new BinaryOperationInstruction(IROpCode.ADD_I32, dest, src, zero));
}
/* ====================================================================== */
/* 控制流指令 */
/* ====================================================================== */
/**
* 生成无条件跳转JMP指令跳转到指定标签
*
* @param ctx 当前 IR 上下文
* @param label 目标标签名
*/
public static void jmp(IRContext ctx, String label) {
ctx.addInstruction(new IRJumpInstruction(label));
}
/**
* IR 中插入一个标签Label
*
* @param ctx 当前 IR 上下文
* @param label 标签名
*/
public static void label(IRContext ctx, String label) {
ctx.addInstruction(new IRLabelInstruction(label));
}
/**
* 比较跳转 if a < b goto label根据条件跳转到目标标签
*
* @param ctx 当前 IR 上下文
* @param cmp 比较操作码 IROpCode.LT_I32
* @param a 第一个操作数寄存器
* @param b 第二个操作数寄存器
* @param targetLabel 跳转目标标签
*/
public static void cmpJump(IRContext ctx, IROpCode cmp,
IRVirtualRegister a, IRVirtualRegister b,
String targetLabel) {
ctx.addInstruction(new IRCompareJumpInstruction(cmp, a, b, targetLabel));
}
/* ---------------- 返回 ---------------- */
/**
* 生成返回指令带返回值
*
* @param ctx 当前 IR 上下文
* @param value 返回值寄存器
*/
public static void ret(IRContext ctx, IRVirtualRegister value) {
ctx.addInstruction(new ReturnInstruction(value));
}
/**
* 生成无返回值的 return 指令 void 函数
*
* @param ctx 当前 IR 上下文
*/
public static void retVoid(IRContext ctx) {
ctx.addInstruction(new ReturnInstruction(null));
}
}

View File

@ -0,0 +1,180 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
import org.jcnc.snow.compiler.ir.utils.IROpCodeUtils;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import org.jcnc.snow.compiler.parser.ast.*;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
/**
* StatementBuilder AST 语句节点 ({@link StatementNode}) 转换为 IR 指令序列的构建器
* <p>
* 负责将各种语句节点循环分支表达式赋值声明返回等生成对应的 IR 指令并管理作用域和控制流标签
* </p>
*/
public class StatementBuilder {
/** 当前 IR 上下文,包含作用域、指令序列等信息。 */
private final IRContext ctx;
/** 表达式 IR 构建器,用于将表达式节点转为 IR 指令。 */
private final ExpressionBuilder expr;
/**
* 构造方法
* @param ctx IR 编译上下文环境
*/
public StatementBuilder(IRContext ctx) {
this.ctx = ctx;
this.expr = new ExpressionBuilder(ctx);
}
/**
* 将一个 AST 语句节点转为 IR 指令序列
* 根据节点类型分发到对应的处理方法
* @param stmt 待转换的语句节点
*/
public void build(StatementNode stmt) {
if (stmt instanceof LoopNode loop) {
// 循环语句
buildLoop(loop);
return;
}
if (stmt instanceof IfNode ifNode) {
// 分支if-else语句
buildIf(ifNode);
return;
}
if (stmt instanceof ExpressionStatementNode(ExpressionNode exp)) {
// 纯表达式语句 foo();
expr.build(exp);
return;
}
if (stmt instanceof AssignmentNode(String var, ExpressionNode rhs)) {
// 赋值语句 a = b + 1;
IRVirtualRegister target = getOrDeclareRegister(var);
expr.buildInto(rhs, target);
return;
}
if (stmt instanceof DeclarationNode decl) {
// 变量声明 int a = 1;
if (decl.getInitializer().isPresent()) {
// 声明同时有初值
IRVirtualRegister r = expr.build(decl.getInitializer().get());
ctx.getScope().declare(decl.getName(), r);
} else {
// 仅声明无初值
ctx.getScope().declare(decl.getName());
}
return;
}
if (stmt instanceof ReturnNode ret) {
// return 语句
if (ret.getExpression().isPresent()) {
// return 带返回值
IRVirtualRegister r = expr.build(ret.getExpression().get());
InstructionFactory.ret(ctx, r);
} else {
// return 无返回值
InstructionFactory.retVoid(ctx);
}
return;
}
// 不支持的语句类型
throw new IllegalStateException("Unsupported statement: " + stmt.getClass().getSimpleName() + ": " + stmt);
}
/**
* 获取变量名对应的寄存器不存在则声明一个新的
* @param name 变量名
* @return 变量对应的虚拟寄存器
*/
private IRVirtualRegister getOrDeclareRegister(String name) {
IRVirtualRegister reg = ctx.getScope().lookup(name);
if (reg == null) {
reg = ctx.newRegister();
ctx.getScope().declare(name, reg);
}
return reg;
}
/**
* 批量构建一组语句节点顺序处理每个语句
* @param stmts 语句节点集合
*/
private void buildStatements(Iterable<StatementNode> stmts) {
for (StatementNode s : stmts) build(s);
}
/**
* 构建循环语句for/while
* 处理流程初始语句 条件判断 循环体 更新语句 跳回条件
* @param loop 循环节点
*/
private void buildLoop(LoopNode loop) {
if (loop.initializer() != null) build(loop.initializer());
String lblStart = ctx.newLabel();
String lblEnd = ctx.newLabel();
// 循环开始标签
InstructionFactory.label(ctx, lblStart);
// 条件不满足则跳出循环
emitConditionalJump(loop.condition(), lblEnd);
// 构建循环体
buildStatements(loop.body());
// 更新部分 for i++
if (loop.update() != null) build(loop.update());
// 跳回循环起点
InstructionFactory.jmp(ctx, lblStart);
// 循环结束标签
InstructionFactory.label(ctx, lblEnd);
}
/**
* 构建分支语句if/else
* 处理流程条件判断 then 分支 else 分支可选
* @param ifNode if 语句节点
*/
private void buildIf(IfNode ifNode) {
String lblElse = ctx.newLabel();
String lblEnd = ctx.newLabel();
// 条件不成立则跳转到 else
emitConditionalJump(ifNode.condition(), lblElse);
// then 分支
buildStatements(ifNode.thenBranch());
// then 分支执行完后直接跳到结束
InstructionFactory.jmp(ctx, lblEnd);
// else 分支可能为空
InstructionFactory.label(ctx, lblElse);
if (ifNode.elseBranch() != null) buildStatements(ifNode.elseBranch());
// 结束标签
InstructionFactory.label(ctx, lblEnd);
}
/**
* 条件跳转指令的生成
* 如果是二元比较表达式直接使用对应比较操作码否则等价于与 0 比较
* @param cond 条件表达式
* @param falseLabel 条件不成立时跳转到的标签
*/
private void emitConditionalJump(ExpressionNode cond, String falseLabel) {
if (cond instanceof BinaryExpressionNode(ExpressionNode left, String operator, ExpressionNode right)
&& ExpressionUtils.isComparisonOperator(operator)) {
// 如果是比较操作 ==, >, <直接生成对应的条件跳转
IRVirtualRegister a = expr.build(left);
IRVirtualRegister b = expr.build(right);
// 获取反向比较操作码
IROpCode falseOp = IROpCodeUtils.invert(ExpressionUtils.cmpOp(operator));
InstructionFactory.cmpJump(ctx, falseOp, a, b, falseLabel);
} else {
// 否则将 cond 0 比较相等则跳转
IRVirtualRegister condReg = expr.build(cond);
IRVirtualRegister zero = InstructionFactory.loadConst(ctx, 0);
InstructionFactory.cmpJump(ctx, IROpCode.CMP_EQ, condReg, zero, falseLabel);
}
}
}

View File

@ -0,0 +1,144 @@
package org.jcnc.snow.compiler.ir.core;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.ArrayList;
import java.util.List;
/**
* 表示单个函数的中间表示IR
* <p>
* IRFunction 跟踪代码生成和优化所需的所有信息
* 包括函数标识符IR 指令序列
* 声明参数列表以及生成唯一虚拟寄存器的机制
* </p>
*/
public class IRFunction {
/**
* 函数名对应源级函数的标识
*/
private final String name;
/**
* IR 指令列表组成函数体
*/
private final List<IRInstruction> body = new ArrayList<>();
/**
* 用于生成新的虚拟寄存器编号的计数器
*/
private int regCounter = 0;
/**
* 正式参数所对应的虚拟寄存器列表按声明顺序排列
*/
private final List<IRVirtualRegister> parameters = new ArrayList<>();
/**
* 构造一个具有指定名称的 IRFunction 实例
*
* @param name 要关联的函数名称
*/
public IRFunction(String name) {
this.name = name;
}
/**
* 分配一个新的虚拟寄存器
* 每次调用会生成一个带有唯一编号的 IRVirtualRegister
*
* @return 新分配的虚拟寄存器
*/
public IRVirtualRegister newRegister() {
return new IRVirtualRegister(regCounter++);
}
/**
* 将一个虚拟寄存器添加到函数的正式参数列表中
* <p>
* 应按源函数签名中参数的声明顺序逐一调用此方法
* </p>
*
* @param vr 表示函数某个参数的虚拟寄存器
*/
public void addParameter(IRVirtualRegister vr) {
parameters.add(vr);
}
/**
* 获取函数正式参数的只读列表
*
* @return 按声明顺序排列的虚拟寄存器列表
*/
public List<IRVirtualRegister> parameters() {
return List.copyOf(parameters);
}
/**
* 向函数体末尾追加一条 IR 指令
*
* @param inst 要追加的 IRInstruction 实例
*/
public void add(IRInstruction inst) {
body.add(inst);
}
/**
* 获取函数体中所有指令的只读列表
*
* @return 表示函数体的 IRInstruction 列表
*/
public List<IRInstruction> body() {
return List.copyOf(body);
}
/**
* 获取函数的源级名称
*
* @return 函数名称
*/
public String name() {
return name;
}
/**
* 获取已分配的虚拟寄存器总数
*
* @return 虚拟寄存器计数
*/
public int registerCount() {
return regCounter;
}
/**
* 以IR代码表示示例
* <pre>
* func 名称(%0, %1, ...) {
* 指令0
* 指令1
* ...
* }
* </pre>
*
* @return 函数的 IR 文本表示
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("func ")
.append(name)
.append('(');
for (int i = 0; i < parameters.size(); i++) {
sb.append(parameters.get(i));
if (i < parameters.size() - 1) {
sb.append(", ");
}
}
sb.append(") {\n");
for (IRInstruction inst : body) {
sb.append(" ").append(inst).append('\n');
}
sb.append('}');
return sb.toString();
}
}

View File

@ -0,0 +1,62 @@
package org.jcnc.snow.compiler.ir.core;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.List;
/**
* IRInstruction 所有 IR中间表示指令的抽象基类
* <p>
* 本类定义了编译器中间表示系统中所有指令的基本结构和行为
* 具体指令通过继承此类并实现各自的操作码Opcode和访问者方法
* 以支持统一的指令处理和访问模式
* </p>
*/
public abstract class IRInstruction {
/**
* 获取该指令的操作码Opcode
* <p>
* 每个具体指令子类必须实现此方法返回对应的 IROpCode 枚举值
* </p>
*
* @return 表示指令类型的 IROpCode 实例
*/
public abstract IROpCode op();
/**
* 获取指令的目标虚拟寄存器destination register
* <p>
* 默认实现返回 null只有具有目标寄存器的指令如赋值算术运算
* 应重写此方法以返回相应的 IRVirtualRegister
* </p>
*
* @return 目标虚拟寄存器若无目标寄存器则返回 null
*/
public IRVirtualRegister dest() {
return null;
}
/**
* 获取指令的操作数列表
* <p>
* 默认实现返回空列表具体指令子类应根据需要重写此方法
* 提供所有参与运算或调用的 IRValue 操作数集合
* </p>
*
* @return 包含本指令所有操作数的列表
*/
public List<IRValue> operands() {
return List.of();
}
/**
* 接受一个 IRVisitor 实例实现访问者模式的入口
* <p>
* 具体指令子类必须实现此方法以便 IRVisitor 根据指令类型
* 调用相应的访问逻辑
* </p>
*
* @param visitor 实现 IRVisitor 接口的访问者对象
*/
public abstract void accept(IRVisitor visitor);
}

View File

@ -0,0 +1,89 @@
package org.jcnc.snow.compiler.ir.core;
/**
* {@code IROpCode} 枚举类型定义了中间表示IR层支持的全部操作码
* <p>
* 每个操作码代表一种低层次语义明确的中间指令用于构建目标函数的中间表示
* 这些操作涵盖了不同数据位宽的整数与浮点数算术运算逻辑与比较操作
* 数据加载与存储指令控制流指令如跳转条件跳转标签
* 以及函数调用与返回等功能
* <p>
* 本枚举用于 {@link IRInstruction} 体系结构中 IR 指令识别和转换的核心部分
* </p>
*/
public enum IROpCode {
// 整型 单精度
CONV_I32_TO_F32,
CONV_F32_TO_I32,
// 整型 双精度
CONV_I32_TO_D64,
CONV_D64_TO_I32,
// 单精度 双精度
CONV_F32_TO_D64,
CONV_D64_TO_F32,
/* ───── 算术运算8位整数byte───── */
ADD_B8, // 8位整型加法a = b + c
SUB_B8, // 8位整型减法a = b - c
MUL_B8, // 8位整型乘法a = b * c
DIV_B8, // 8位整型除法a = b / c
NEG_B8, // 8位整型取负a = -b
/* ───── 算术运算16位整数short───── */
ADD_S16, // 16位整型加法
SUB_S16, // 16位整型减法
MUL_S16, // 16位整型乘法
DIV_S16, // 16位整型除法
NEG_S16, // 16位整型取负
/* ───── 算术运算32位整数int───── */
ADD_I32, // 32位整型加法
SUB_I32, // 32位整型减法
MUL_I32, // 32位整型乘法
DIV_I32, // 32位整型除法
NEG_I32, // 32位整型取负
/* ───── 算术运算64位整数long───── */
ADD_L64, // 64位整型加法
SUB_L64, // 64位整型减法
MUL_L64, // 64位整型乘法
DIV_L64, // 64位整型除法
NEG_L64, // 64位整型取负
/* ───── 算术运算32位浮点数float───── */
ADD_F32, // 32位浮点加法
SUB_F32, // 32位浮点减法
MUL_F32, // 32位浮点乘法
DIV_F32, // 32位浮点除法
NEG_F32, // 32位浮点取负
/* ───── 算术运算64位浮点数double───── */
ADD_D64, // 64位浮点加法
SUB_D64, // 64位浮点减法
MUL_D64, // 64位浮点乘法
DIV_D64, // 64位浮点除法
NEG_D64, // 64位浮点取负
/* ───── 逻辑与比较运算指令 ───── */
CMP_EQ, // 相等比较a == b
CMP_NE, // 不等比较a != b
CMP_LT, // 小于比较a < b
CMP_GT, // 大于比较a > b
CMP_LE, // 小于等于a <= b
CMP_GE, // 大于等于a >= b
/* ───── 数据访问与常量操作 ───── */
LOAD, // 从内存加载数据至寄存器
STORE, // 将寄存器数据写回内存
CONST, // 将常量写入目标寄存器
/* ───── 控制流指令 ───── */
JUMP, // 无条件跳转至标签
LABEL, // 标签定义
/* ───── 函数调用与返回 ───── */
CALL, // 函数调用
RET // 函数返回
}

View File

@ -0,0 +1,50 @@
package org.jcnc.snow.compiler.ir.core;
import java.util.Map;
/**
* 操作符与IR操作码映射表统一管理所有类型的算术和比较操作映射
*/
public final class IROpCodeMappings {
private IROpCodeMappings() {} // 禁止实例化
// 8位整型运算符映射
public static final Map<String, IROpCode> OP_I8 = Map.of(
"+", IROpCode.ADD_B8, "-", IROpCode.SUB_B8,
"*", IROpCode.MUL_B8, "/", IROpCode.DIV_B8
);
// 16位整型
public static final Map<String, IROpCode> OP_I16 = Map.of(
"+", IROpCode.ADD_S16, "-", IROpCode.SUB_S16,
"*", IROpCode.MUL_S16, "/", IROpCode.DIV_S16
);
// 32位整型
public static final Map<String, IROpCode> OP_I32 = Map.of(
"+", IROpCode.ADD_I32, "-", IROpCode.SUB_I32,
"*", IROpCode.MUL_I32, "/", IROpCode.DIV_I32
);
// 64位长整型
public static final Map<String, IROpCode> OP_L64 = Map.of(
"+", IROpCode.ADD_L64, "-", IROpCode.SUB_L64,
"*", IROpCode.MUL_L64, "/", IROpCode.DIV_L64
);
// 32位浮点型
public static final Map<String, IROpCode> OP_F32 = Map.of(
"+", IROpCode.ADD_F32, "-", IROpCode.SUB_F32,
"*", IROpCode.MUL_F32, "/", IROpCode.DIV_F32
);
// 64位双精度浮点型
public static final Map<String, IROpCode> OP_D64 = Map.of(
"+", IROpCode.ADD_D64, "-", IROpCode.SUB_D64,
"*", IROpCode.MUL_D64, "/", IROpCode.DIV_D64
);
// 比较操作符映射
public static final Map<String, IROpCode> CMP = Map.of(
"==", IROpCode.CMP_EQ,
"!=", IROpCode.CMP_NE,
"<", IROpCode.CMP_LT,
">", IROpCode.CMP_GT,
"<=", IROpCode.CMP_LE,
">=", IROpCode.CMP_GE
);
}

View File

@ -0,0 +1,59 @@
package org.jcnc.snow.compiler.ir.core;
import org.jcnc.snow.compiler.ir.instruction.IRAddInstruction;
import org.jcnc.snow.compiler.ir.instruction.IRJumpInstruction;
import org.jcnc.snow.compiler.ir.instruction.IRReturnInstruction;
/**
* {@code IRPrinter} 是一个用于打印 IR 指令的访问者实现
* <p>
* 本类实现 {@link IRVisitor} 接口通过覆盖各类指令的访问方法
* 提供对不同类型 IR 指令的格式化输出通常用于调试或测试
* 默认行为是在控制台System.out输出指令的基本信息
* </p>
* <p>
* 可通过继承该类进一步扩展对更多指令类型的支持或重写输出格式以适配不同的前端/后端需求
* </p>
*/
public abstract class IRPrinter implements IRVisitor {
/**
* 访问 {@link IRAddInstruction} 加法指令
* <p>
* 默认输出形式为 "Add: <inst>"其中 <inst> 为指令对象的字符串表示
* </p>
*
* @param inst 加法 IR 指令实例
*/
@Override
public void visit(IRAddInstruction inst) {
System.out.println("Add: " + inst);
}
/**
* 访问 {@link IRJumpInstruction} 跳转指令
* <p>
* 默认输出形式为 "Jump: <inst>"
* </p>
*
* @param inst 跳转 IR 指令实例
*/
@Override
public void visit(IRJumpInstruction inst) {
System.out.println("Jump: " + inst);
}
/**
* 访问 {@link IRReturnInstruction} 返回指令
* <p>
* 默认输出形式为 "Return: <inst>"
* </p>
*
* @param inst 返回 IR 指令实例
*/
@Override
public void visit(IRReturnInstruction inst) {
System.out.println("Return: " + inst);
}
}

View File

@ -0,0 +1,62 @@
package org.jcnc.snow.compiler.ir.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* {@code IRProgram} 表示一份完整的中间表示Intermediate Representation, IR程序
* <p>
* 它作为编译器后端处理阶段的核心结构承载所有由源代码翻译得到的 {@link IRFunction} 实例
* 形成整体性的中间表示单元便于进行后续的优化目标代码生成或静态分析
* </p>
*/
public final class IRProgram {
/**
* 存储程序中所有函数的有序集合
*/
private final List<IRFunction> functions = new ArrayList<>();
/**
* 将一个 {@link IRFunction} 添加到程序中
* <p>
* 函数会按添加顺序保留在内部集合中
* </p>
*
* @param irFunction 要加入的 IR 函数对象
*/
public void add(IRFunction irFunction) {
functions.add(irFunction);
}
/**
* 获取程序中全部函数的只读视图
* <p>
* 外部调用者无法通过返回的列表修改函数集合从而确保封装性与结构完整性
* </p>
*
* @return 不可变的函数列表
*/
public List<IRFunction> functions() {
return Collections.unmodifiableList(functions);
}
/**
* 返回该 IR 程序的字符串形式
* <p>
* 每个函数按其 {@code toString()} 表示输出换行分隔
* 通常用于调试与日志输出
* </p>
*
* @return 表示整个 IR 程序的格式化字符串
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (IRFunction f : functions) {
sb.append(f).append('\n');
}
return sb.toString();
}
}

View File

@ -0,0 +1,29 @@
package org.jcnc.snow.compiler.ir.core;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRLabel;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
/**
* {@code IRValue} 表示 IR 指令系统中可被操作的值类型
* <p>
* 它定义了所有 IR 指令在使用操作数参数结果或跳转目标时的统一抽象
* 实现该接口的类型可以作为 {@link IRInstruction} 中的操作数出现
* </p>
*
* <p>当前支持的 IR 值类型包括</p>
* <ul>
* <li>{@link IRVirtualRegister}虚拟寄存器表示计算结果或中间变量</li>
* <li>{@link IRConstant}常量值表示不可变的字面量或数值</li>
* <li>{@link IRLabel}标签表示跳转指令的目标地址</li>
* </ul>
*
* <p>
* 该接口声明为 {@code sealed interface}限制只能被上述类型实现
* 这种设计允许编译器对 {@code IRValue} 的使用进行静态穷尽性检查
* 有助于提升类型安全性与维护性
* </p>
*/
public sealed interface IRValue
permits IRVirtualRegister, IRConstant, IRLabel {
}

View File

@ -0,0 +1,80 @@
package org.jcnc.snow.compiler.ir.core;
import org.jcnc.snow.compiler.ir.instruction.*;
/**
* {@code IRVisitor} 是中间表示IR指令体系的访问者接口
* <p>
* 它定义了访问者模式的核心机制通过对每种 {@link IRInstruction} 子类
* 提供独立的 {@code visit} 方法实现对指令的分发与处理
* 不同的访问者实现可用于执行不同任务例如
* </p>
* <ul>
* <li>{@code IRPrinter}打印指令内容</li>
* <li>{@code IROptimizer}分析与重写 IR 以优化性能</li>
* <li>{@code IRCodeGenerator}生成平台相关的机器码或汇编代码</li>
* </ul>
*
* <p>
* 每当添加新的 {@code IRInstruction} 子类应同步扩展该接口
* 以确保访问行为的一致性与完整性
* </p>
*/
public interface IRVisitor {
/**
* 访问加法指令示例实现
*
* @param inst 加法指令实例
*/
void visit(IRAddInstruction inst);
/**
* 访问跳转指令
*
* @param inst 跳转指令实例
*/
void visit(IRJumpInstruction inst);
/**
* 访问返回指令无返回值
*
* @param inst 返回指令实例
*/
void visit(IRReturnInstruction inst);
/**
* 访问二元运算指令如加减乘除等
*
* @param inst 二元运算 IR 指令实例
*/
void visit(BinaryOperationInstruction inst);
/**
* 访问加载常量的指令
*
* @param inst 常量加载指令实例
*/
void visit(LoadConstInstruction inst);
/**
* 访问返回指令支持返回值
*
* @param inst 通用返回指令实例
*/
void visit(ReturnInstruction inst);
/**
* 访问一元运算指令如取负等
*
* @param inst 一元运算指令实例
*/
void visit(UnaryOperationInstruction inst);
/**
* 访问函数调用指令
*
* @param instruction 函数调用指令实例
*/
void visit(CallInstruction instruction);
}

View File

@ -0,0 +1,37 @@
# Snow Compiler - IR 模块
> Snow 编译器的中间表示模块 —— 负责构建、组织和输出中间表示指令IR
## 项目简介
**IRIntermediate Representation** 是 [Snow 编译器]() 项目的核心模块,承担中间表示的构建、组织与管理任务。
它用于在前端语法分析与后端目标代码生成之间,提供结构清晰、便于优化和转换的抽象表示形式。
IR 模块以类 SSAStatic Single Assignment形式设计通过统一的指令体系、虚拟寄存器模型和构建器架构实现了良好的表达力与可扩展性为后续优化和代码生成阶段打下基础。
## 核心功能
* **统一的中间表示模型**:表达控制流与数据流,支持函数、指令、值等核心结构
* **IR 构建器体系**:模块化构建函数、表达式与语句 IR简化前端对接
* **灵活的指令层级结构**:支持二元操作、跳转、返回等多种基本指令
* **寄存器与常量模型**:统一管理虚拟寄存器、常量、标签等值类型
* **IR 打印与调试支持**:辅助输出 IR 文本格式,支持可视化与调试
## 模块结构
```
ir/
├── builder/ // 构建器模块:负责构造表达式、函数与语句的 IR
├── core/ // 核心定义IR 基础结构,如函数、指令、程序、访问器等
├── instruction/ // 指令实现:具体的 IR 指令类型(如加法、跳转、返回等)
├── utils/ // 工具模块:提供 IR 操作相关的辅助函数(如表达式工具、操作码工具等)
└── value/ // 值模型:常量、标签、虚拟寄存器等
```
## 开发环境
* JDK 23 或更高版本
* Maven 构建管理
* 推荐 IDEIntelliJ IDEA
---

View File

@ -0,0 +1,97 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.List;
/**
* BinaryOperationInstruction 表示一个二元运算指令格式为dest = lhs OP rhs
* <p>
* 该类用于描述形如 a = b + c a = x * y 的二元运算指令
* 运算类型OP {@link IROpCode} 指定包括加法减法乘法除法等
* 左右操作数为 IRValue 类型结果保存在目标虚拟寄存器 dest
*/
public final class BinaryOperationInstruction extends IRInstruction {
/** 指令操作符,如 ADD_I32、SUB_I32 等,取自 IROpCode 枚举 */
private final IROpCode op;
/** 运算结果将写入的目标虚拟寄存器 */
private final IRVirtualRegister dest;
/** 运算的左操作数 */
private final IRValue lhs;
/** 运算的右操作数 */
private final IRValue rhs;
/**
* 构造函数创建一个完整的二元运算指令
*
* @param op 运算类型除等
* @param dest 运算结果的目标寄存器
* @param lhs 左操作数
* @param rhs 右操作数
*/
public BinaryOperationInstruction(IROpCode op, IRVirtualRegister dest, IRValue lhs, IRValue rhs) {
this.op = op;
this.dest = dest;
this.lhs = lhs;
this.rhs = rhs;
}
/**
* 获取该指令的操作符
*
* @return 运算类型IROpCode
*/
@Override
public IROpCode op() {
return op;
}
/**
* 获取该指令的目标寄存器
*
* @return 运算结果将写入的虚拟寄存器
*/
@Override
public IRVirtualRegister dest() {
return dest;
}
/**
* 获取该指令使用的操作数
*
* @return 一个包含左右操作数的列表
*/
@Override
public List<IRValue> operands() {
return List.of(lhs, rhs);
}
/**
* 转换为字符串格式便于调试与打印
* v1 = ADD_I32 v2, v3
*
* @return 指令的字符串表示形式
*/
@Override
public String toString() {
return dest + " = " + op + " " + lhs + ", " + rhs;
}
/**
* 接受访问者对象实现访问者模式分发逻辑
*
* @param visitor 实现 IRVisitor 的访问者对象
*/
@Override
public void accept(IRVisitor visitor) {
visitor.visit(this);
}
}

View File

@ -0,0 +1,64 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import org.jcnc.snow.compiler.ir.core.IRValue;
import java.util.ArrayList;
import java.util.List;
/**
* CallInstruction 表示一次函数调用格式dest = CALL functionName, arg1, arg2, ...
*/
public class CallInstruction extends IRInstruction {
private final IRVirtualRegister dest;
private final String functionName;
private final List<IRValue> arguments;
public CallInstruction(IRVirtualRegister dest, String functionName, List<IRValue> args) {
this.dest = dest;
this.functionName = functionName;
this.arguments = List.copyOf(args);
}
@Override
public IROpCode op() {
return IROpCode.CALL;
}
@Override
public List<IRValue> operands() {
List<IRValue> ops = new ArrayList<>();
ops.add(dest);
ops.addAll(arguments);
return ops;
}
public IRVirtualRegister getDest() {
return dest;
}
public String getFunctionName() {
return functionName;
}
public List<IRValue> getArguments() {
return arguments;
}
@Override
public void accept(IRVisitor visitor) {
visitor.visit(this);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(dest + " = CALL " + functionName);
for (IRValue arg : arguments) {
sb.append(", ").append(arg);
}
return sb.toString();
}
}

View File

@ -0,0 +1,92 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.List;
/**
* IRAddInstruction 表示一个加法指令形如dest = lhs + rhs
* <p>
* 本类是一个具体的 IRInstruction 子类表示将两个值相加并将结果写入目标寄存器的操作
* 虽然功能与通用的 {@link BinaryOperationInstruction} 类似但它作为更简化明确的指令实现
* 通常用于测试或示例用途也可为特殊优化保留独立形式
*/
public class IRAddInstruction extends IRInstruction {
/** 运算结果存放的目标虚拟寄存器 */
private final IRVirtualRegister dest;
/** 左操作数 */
private final IRValue lhs;
/** 右操作数 */
private final IRValue rhs;
/**
* 构造函数创建加法指令实例
*
* @param dest 运算结果的存储位置
* @param lhs 加法左操作数
* @param rhs 加法右操作数
*/
public IRAddInstruction(IRVirtualRegister dest, IRValue lhs, IRValue rhs) {
this.dest = dest;
this.lhs = lhs;
this.rhs = rhs;
}
/**
* 返回该指令的操作码ADD_I32
*
* @return 加法操作码
*/
@Override
public IROpCode op() {
return IROpCode.ADD_I32;
}
/**
* 获取指令的目标寄存器
*
* @return 运算结果存放的虚拟寄存器
*/
@Override
public IRVirtualRegister dest() {
return dest;
}
/**
* 获取加法指令的两个操作数
*
* @return 包含左右操作数的列表
*/
@Override
public List<IRValue> operands() {
return List.of(lhs, rhs);
}
/**
* 使用访问者处理当前加法指令
*
* @param visitor 实现 IRVisitor 的访问者对象
*/
@Override
public void accept(IRVisitor visitor) {
visitor.visit(this);
}
/**
* 返回指令的字符串形式方便调试
* 例如v1 = v2 + v3
*
* @return 字符串表示形式
*/
@Override
public String toString() {
return dest + " = " + lhs + " + " + rhs;
}
}

View File

@ -0,0 +1,47 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
/**
* 比较 + 条件跳转 复合指令
* if ( left <cmpOp> right ) jump targetLabel;
* <p>
* 其中 cmpOp 只能是 IROpCode.CMP_* 六种比较操作码
*/
public final class IRCompareJumpInstruction extends IRInstruction {
private final IROpCode cmpOp; // CMP_EQ / CMP_NE / CMP_LT / ...
private final IRVirtualRegister left, right; // 两个比较操作数
private final String targetLabel; // 跳转目标
public IRCompareJumpInstruction(IROpCode cmpOp,
IRVirtualRegister left,
IRVirtualRegister right,
String targetLabel) {
this.cmpOp = cmpOp;
this.left = left;
this.right = right;
this.targetLabel = targetLabel;
}
@Override
public IROpCode op() {
return cmpOp;
}
public IRVirtualRegister left() { return left; }
public IRVirtualRegister right() { return right; }
public String label() { return targetLabel; }
@Override
public String toString() {
return cmpOp + " " + left + ", " + right + " -> " + targetLabel;
}
/** 暂无访问者实现,留空 */
@Override
public void accept(IRVisitor visitor) { /* no-op */ }
}

View File

@ -0,0 +1,66 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
/**
* IRJumpInstruction 表示一个无条件跳转jump IR 指令
* <p>
* 该指令用于控制流结构中实现无条件跳转到指定标签label
* if-else循环函数跳转等高级语言结构翻译到中间表示的重要组成部分
*/
public class IRJumpInstruction extends IRInstruction {
/** 跳转目标的标签名 */
private final String label;
/**
* 构造函数创建跳转指令
*
* @param label 跳转目标标签的名称
*/
public IRJumpInstruction(String label) {
this.label = label;
}
/**
* 获取该指令对应的操作码JUMP
*
* @return IROpCode.JUMP
*/
@Override
public IROpCode op() {
return IROpCode.JUMP;
}
/**
* 获取跳转目标标签名
*
* @return 标签名称字符串
*/
public String label() {
return label;
}
/**
* 接受访问者用于访问者模式处理
*
* @param visitor 实现 IRVisitor 的访问者实例
*/
@Override
public void accept(IRVisitor visitor) {
visitor.visit(this);
}
/**
* 将指令转为字符串形式便于打印与调试
* 例如jump L1
*
* @return 指令的字符串表示
*/
@Override
public String toString() {
return "jump " + label;
}
}

View File

@ -0,0 +1,39 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
/**
* IR 中标记一个代码位置用于 Jump / 条件跳转目标
* <p>
* 生成到 VM 时并不会真正发出可执行指令
* 仅在 {@code VMCodeGenerator} 内部被用来计算真实地址
*/
public final class IRLabelInstruction extends IRInstruction {
private final String name;
public IRLabelInstruction(String name) {
this.name = name;
}
@Override
public IROpCode op() {
return IROpCode.LABEL;
}
public String name() {
return name;
}
@Override
public String toString() {
return name + ":";
}
/** 目前尚未对 Label 做访问者处理,空实现即可 */
@Override
public void accept(IRVisitor visitor) {
/* no-op */
}
}

View File

@ -0,0 +1,71 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
import java.util.List;
/**
* IRReturnInstruction 表示一个带返回值的返回ret指令
* <p>
* 此指令用于函数结束时将某个值作为返回结果返回给调用者
* 返回值可以是常量寄存器或表达式的结果
* 若不返回值也可以扩展为 null 代表 void 函数
*/
public class IRReturnInstruction extends IRInstruction {
/** 要返回的值,可以是常量、虚拟寄存器等 */
private final IRValue returnValue;
/**
* 构造函数创建返回指令
*
* @param returnValue 函数的返回值
*/
public IRReturnInstruction(IRValue returnValue) {
this.returnValue = returnValue;
}
/**
* 获取该指令的操作码RET
*
* @return IROpCode.RET表示返回操作
*/
@Override
public IROpCode op() {
return IROpCode.RET;
}
/**
* 返回该指令的操作数列表仅包含返回值
*
* @return 含一个元素的列表即返回值
*/
@Override
public List<IRValue> operands() {
return List.of(returnValue);
}
/**
* 接受访问者处理该指令适用于访问者模式
*
* @param visitor 实现了 IRVisitor 的访问者对象
*/
@Override
public void accept(IRVisitor visitor) {
visitor.visit(this);
}
/**
* 转换为字符串形式便于调试与打印
* 示例ret v1
*
* @return 字符串形式的返回指令
*/
@Override
public String toString() {
return "ret " + returnValue;
}
}

View File

@ -0,0 +1,87 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.List;
/**
* LoadConstInstruction 表示一个常量加载指令格式为dest = CONST k
* <p>
* 该指令的功能是将一个常量字面量或编译期已知值加载到一个虚拟寄存器中
* 供后续指令使用例如在表达式计算参数传递初始化等场景中常用
*/
public final class LoadConstInstruction extends IRInstruction {
/** 要加载的常量值,类型为 IRConstant */
private final IRConstant k;
/** 存放常量结果的目标虚拟寄存器 */
private final IRVirtualRegister dest;
/**
* 构造函数创建常量加载指令
*
* @param dest 存放常量的目标虚拟寄存器
* @param k 要加载的常量值
*/
public LoadConstInstruction(IRVirtualRegister dest, IRConstant k) {
this.dest = dest;
this.k = k;
}
/**
* 获取该指令的操作码固定为 CONST
*
* @return IROpCode.CONST
*/
@Override
public IROpCode op() {
return IROpCode.CONST;
}
/**
* 获取指令的目标虚拟寄存器
*
* @return 用于存放常量的寄存器
*/
@Override
public IRVirtualRegister dest() {
return dest;
}
/**
* 获取该指令的操作数仅包含要加载的常量
*
* @return 含一个元素k的操作数列表
*/
@Override
public List<IRValue> operands() {
return List.of(k);
}
/**
* 返回该指令的字符串形式便于调试或打印
* 例如v1 = CONST 42
*
* @return 指令的字符串表示
*/
@Override
public String toString() {
return dest + " = CONST " + k;
}
/**
* 接受访问者模式处理
*
* @param visitor 实现 IRVisitor 的访问者对象
*/
@Override
public void accept(IRVisitor visitor) {
visitor.visit(this);
}
}

View File

@ -0,0 +1,90 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.List;
/**
* ReturnInstruction 表示函数返回指令格式RET RET <value>
* <p>
* 此类用于描述函数执行完毕后的返回操作支持两种返回形式
* - 无返回值void生成无参的 RET 指令
* - 有返回值将指定虚拟寄存器中的值返回给调用者
* <p>
* {@link IRReturnInstruction} 类似但更通用适配多种函数返回风格
*/
public final class ReturnInstruction extends IRInstruction {
/**
* 返回值所在的虚拟寄存器
* 如果为 null则代表函数无返回值 void
*/
private final IRVirtualRegister value;
/**
* 构造函数创建返回指令实例
*
* @param value 若函数有返回值传入对应虚拟寄存器
* 若为 void 函数则传 null
*/
public ReturnInstruction(IRVirtualRegister value) {
this.value = value;
}
/**
* 返回该指令的操作码类型RET
*
* @return IROpCode.RET
*/
@Override
public IROpCode op() {
return IROpCode.RET;
}
/**
* 获取该指令的操作数
* 如果为 void 返回则返回空列表
* 否则返回一个仅包含返回寄存器的列表
*
* @return 操作数列表
*/
@Override
public List<IRValue> operands() {
return value == null ? List.of() : List.of(value);
}
/**
* 获取返回值所在的虚拟寄存器如有
*
* @return 返回值寄存器 null表示 void
*/
public IRVirtualRegister value() {
return value;
}
/**
* 转换为字符串形式便于调试与输出
* - 无返回值RET
* - 有返回值RET v1
*
* @return 字符串表示的返回指令
*/
@Override
public String toString() {
return value == null ? "RET" : "RET " + value;
}
/**
* 接受访问者对象实现访问者模式分发
*
* @param visitor 实现 IRVisitor 的访问者
*/
@Override
public void accept(IRVisitor visitor) {
visitor.visit(this);
}
}

View File

@ -0,0 +1,97 @@
package org.jcnc.snow.compiler.ir.instruction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.core.IRVisitor;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.List;
/**
* UnaryOperationInstruction 表示一个一元运算指令格式dest = OP val
* <p>
* 用于对单个操作数 val 执行指定的一元运算 OP例如取负 NEG
* 并将结果写入目标虚拟寄存器 dest
* <p>
* 支持的操作由 {@link IROpCode} 定义目前常见的一元操作包括
* <ul>
* <li>NEG_I32 整数取负dest = -val</li>
* <li>可扩展逻辑非按位非等</li>
* </ul>
*/
public final class UnaryOperationInstruction extends IRInstruction {
/** 一元运算操作符(如 NEG_I32 */
private final IROpCode op;
/** 运算结果写入的目标虚拟寄存器 */
private final IRVirtualRegister dest;
/** 被操作的值(唯一操作数) */
private final IRValue val;
/**
* 构造函数创建一元运算指令
*
* @param op 一元运算操作符
* @param dest 运算结果目标寄存器
* @param val 参与运算的操作数
*/
public UnaryOperationInstruction(IROpCode op, IRVirtualRegister dest, IRValue val) {
this.op = op;
this.dest = dest;
this.val = val;
}
/**
* 获取该指令的操作码
*
* @return 一元运算的操作码 NEG_I32
*/
@Override
public IROpCode op() {
return op;
}
/**
* 获取该指令的目标寄存器
*
* @return 运算结果的目标寄存器
*/
@Override
public IRVirtualRegister dest() {
return dest;
}
/**
* 获取指令的操作数仅一个
*
* @return 单元素列表仅包含 val
*/
@Override
public List<IRValue> operands() {
return List.of(val);
}
/**
* 将该指令格式化为字符串便于打印与调试
* 形式dest = OP val例如v1 = NEG v2
*
* @return 字符串形式的指令
*/
@Override
public String toString() {
return dest + " = " + op + " " + val;
}
/**
* 接受访问者访问该指令实现访问者模式
*
* @param visitor 访问者实例
*/
@Override
public void accept(IRVisitor visitor) {
visitor.visit(this);
}
}

View File

@ -0,0 +1,202 @@
package org.jcnc.snow.compiler.ir.utils;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import org.jcnc.snow.compiler.ir.core.IROpCodeMappings;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.parser.ast.BinaryExpressionNode;
import org.jcnc.snow.compiler.parser.ast.NumberLiteralNode;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import java.util.Map;
/**
* 表达式分析与运算符辅助工具类
* <p>
* 主要功能包括
* <ul>
* <li>字面量常量的解析与类型推断</li>
* <li>自动匹配操作码</li>
* <li>表达式类型合并与判定</li>
* </ul>
*/
public class ExpressionUtils {
/** 用于存储默认的类型后缀(如函数返回类型),线程隔离。 */
private static final ThreadLocal<Character> DEFAULT_SUFFIX =
ThreadLocal.withInitial(() -> '\0');
/**
* 在进入函数 IR 构建前设置默认的类型后缀比如函数返回类型
* @param suffix 默认后缀字符 'i', 'l', 'f', 'd'
*/
public static void setDefaultSuffix(char suffix) {
DEFAULT_SUFFIX.set(suffix);
}
/**
* 在函数 IR 构建结束后清除默认后缀避免影响后续分析
*/
public static void clearDefaultSuffix() {
DEFAULT_SUFFIX.set('\0');
}
/**
* 解析整数字面量字符串自动去除类型后缀b/s/l/f/d/B/S/L/F/D并转换为 int
*
* @param literal 字面量字符串 "123", "123l", "42B"
* @return 解析得到的 int 整数
*/
public static int parseIntSafely(String literal) {
// 去掉类型后缀只保留数字部分
String digits = literal.replaceAll("[bslfdBSDLF]$", "");
return Integer.parseInt(digits);
}
/**
* 根据数字字面量字符串自动判断类型生成对应类型的 IRConstant
* 支持 b/s/l/f/d 类型后缀与浮点格式自动分配合适类型
*
* @param value 字面量字符串 "1", "2l", "3.14f", "5D"
* @return 生成的 IRConstant 对象包含正确类型
*/
public static IRConstant buildNumberConstant(String value) {
char suffix = value.isEmpty() ? '\0' : Character.toLowerCase(value.charAt(value.length() - 1));
String digits = switch (suffix) {
case 'b','s','l','f','d' -> value.substring(0, value.length() - 1);
default -> value;
};
// 根据类型后缀或数值格式创建常量
return switch (suffix) {
case 'b' -> new IRConstant(Byte.parseByte(digits));
case 's' -> new IRConstant(Short.parseShort(digits));
case 'l' -> new IRConstant(Long.parseLong(digits));
case 'f' -> new IRConstant(Float.parseFloat(digits));
case 'd' -> new IRConstant(Double.parseDouble(digits));
default -> looksLikeFloat(digits)
? new IRConstant(Double.parseDouble(digits))
: new IRConstant(Integer.parseInt(digits));
};
}
/* =================== 类型推断与操作符匹配 =================== */
/**
* 递归推断单个表达式节点的类型后缀b/s/l/f/d
* 对于二元表达式将左右两侧的类型自动提升合并遵循优先级顺序d > f > l > s > b > '\0'
*
* @param node 表达式节点
* @return 类型后缀字符b/s/l/f/d '\0'
*/
private static char typeChar(ExpressionNode node) {
// 字面量节点直接判断最后一位
if (node instanceof NumberLiteralNode(String value)) {
char last = Character.toLowerCase(value.charAt(value.length() - 1));
return switch (last) {
case 'b', 's', 'l', 'f', 'd' -> last;
default -> looksLikeFloat(value) ? 'd' : '\0';
};
}
// 二元表达式递归判断左右子节点
if (node instanceof BinaryExpressionNode bin) {
char l = typeChar(bin.left());
char r = typeChar(bin.right());
return maxTypeChar(l, r);
}
// 其他情况如变量节点暂不处理默认返回 '\0'
return '\0';
}
/**
* 推断两个表达式节点合并后的类型后缀
* 返回优先级更高的类型后缀字符
*
* @param left 左表达式节点
* @param right 右表达式节点
* @return 合并后类型的后缀字符
*/
public static char resolveSuffix(ExpressionNode left, ExpressionNode right) {
return maxTypeChar(typeChar(left), typeChar(right));
}
/**
* 在两个类型后缀中选取精度更高的一个
* 优先级依次为d > f > l > s > b > '\0'
*
* @param l 左类型后缀
* @param r 右类型后缀
* @return 更高优先级的类型后缀字符
*/
private static char maxTypeChar(char l, char r) {
if (l == 'd' || r == 'd') return 'd';
if (l == 'f' || r == 'f') return 'f';
if (l == 'l' || r == 'l') return 'l';
if (l == 's' || r == 's') return 's';
if (l == 'b' || r == 'b') return 'b';
return '\0';
}
/**
* 判断给定字符串是否为比较运算符 >, <, ==
*
* @param op 操作符字符串
* @return 如果是比较操作符返回 true否则返回 false
*/
public static boolean isComparisonOperator(String op) {
return IROpCodeMappings.CMP.containsKey(op);
}
/**
* 获取比较操作符对应的中间表示操作码IROpCode
*
* @param op 比较操作符字符串
* @return 对应的 IROpCode如果不存在则返回 null
*/
public static IROpCode cmpOp(String op) {
return IROpCodeMappings.CMP.get(op);
}
/**
* 根据操作符和两侧表达式自动选择正确的 IROpCode
* 首先根据参与表达式类型推断后缀若无法推断则回退到函数默认类型
* 还无法推断则默认使用 i3232位整型
*
* @param op 操作符字符串 "+"
* @param left 左侧表达式节点
* @param right 右侧表达式节点
* @return 匹配的 IROpCode如果不存在则返回 null
*/
public static IROpCode resolveOpCode(String op,
ExpressionNode left,
ExpressionNode right) {
/* 1) 尝试从参与者常量字面量推断 */
char suffix = resolveSuffix(left, right);
/* 2) 若无法推断退回到函数返回类型DEFAULT_SUFFIX */
if (suffix == '\0') {
suffix = DEFAULT_SUFFIX.get();
}
/* 3) 再次失败则默认为 i32 */
Map<String, IROpCode> table = switch (suffix) {
case 'b' -> IROpCodeMappings.OP_I8;
case 's' -> IROpCodeMappings.OP_I16;
case 'l' -> IROpCodeMappings.OP_L64;
case 'f' -> IROpCodeMappings.OP_F32;
case 'd' -> IROpCodeMappings.OP_D64;
default -> IROpCodeMappings.OP_I32;
};
return table.get(op);
}
/**
* 判断字符串是否为浮点数形式即包含小数点或科学计数法 e/E
*
* @param digits 数字字符串
* @return 如果看起来像浮点数则返回 true否则返回 false
*/
private static boolean looksLikeFloat(String digits) {
return digits.indexOf('.') >= 0 || digits.indexOf('e') >= 0 || digits.indexOf('E') >= 0;
}
}

View File

@ -0,0 +1,26 @@
package org.jcnc.snow.compiler.ir.utils;
import org.jcnc.snow.compiler.ir.core.IROpCode;
import java.util.Map;
/**
* IR 操作码辅助工具
*/
public class IROpCodeUtils {
private static final Map<IROpCode, IROpCode> INVERT = Map.of(
IROpCode.CMP_EQ, IROpCode.CMP_NE,
IROpCode.CMP_NE, IROpCode.CMP_EQ,
IROpCode.CMP_LT, IROpCode.CMP_GE,
IROpCode.CMP_GE, IROpCode.CMP_LT,
IROpCode.CMP_GT, IROpCode.CMP_LE,
IROpCode.CMP_LE, IROpCode.CMP_GT
);
/**
* 获取给定比较操作的相反操作码
*/
public static IROpCode invert(IROpCode op) {
return INVERT.get(op);
}
}

View File

@ -0,0 +1,31 @@
package org.jcnc.snow.compiler.ir.value;
import org.jcnc.snow.compiler.ir.core.IRValue;
/**
* IRConstant 表示中间表示IR系统中的常量值
* <p>
* 常量用于表示在编译期间已知的不可变值例如字面整数浮点数布尔值或字符串
* {@link IRVirtualRegister} 不同常量不需要通过寄存器存储
* 可直接作为 IR 指令的操作数使用
* <p>
* 典型应用
* - 加载常量指令v1 = CONST 42
* - 计算表达式v2 = ADD v1, 100
*/
public record IRConstant(Object value) implements IRValue {
/**
* 将常量值转换为字符串用于打印 IR 指令或调试输出
* <p>
* 例如
* - 整数常量42
* - 字符串常量"hello"
*
* @return 常量的字符串表示
*/
@Override
public String toString() {
return value.toString();
}
}

View File

@ -0,0 +1,29 @@
package org.jcnc.snow.compiler.ir.value;
import org.jcnc.snow.compiler.ir.core.IRValue;
/**
* IRLabel 表示中间表示IR系统中的跳转目标标签
* <p>
* 标签用于控制流指令 JUMP等
* 作为程序执行跳转的目的地 IR 控制流图CFG中的基本构建块
* <p>
* 每个标签由一个唯一的名称String name标识
* 可用于生成目标代码中的符号标签或跳转地址
* <p>
* 该类实现了 {@link IRValue} 接口因此也可被视为指令操作数
* 在某些 IRInstruction 中以参数形式出现如条件跳转目标
*/
public record IRLabel(String name) implements IRValue {
/**
* 返回标签的字符串形式便于打印或调试
* 通常表示为带冒号的形式例如 "L1:"
*
* @return 格式化后的标签字符串
*/
@Override
public String toString() {
return name + ":";
}
}

View File

@ -0,0 +1,34 @@
package org.jcnc.snow.compiler.ir.value;
import org.jcnc.snow.compiler.ir.core.IRValue;
/**
* IRVirtualRegister 表示一个静态单赋值SSA形式的虚拟寄存器
* <p>
* IR 系统中虚拟寄存器用于存储每个中间计算结果 SSAStatic Single Assignment形式的核心
* 每个虚拟寄存器在程序中只被赋值一次其值来源于一条明确的指令输出
* <p>
* 特点
* <ul>
* <li>每个寄存器有唯一编号 {@code id} {@code IRFunction.newRegister()} 自动生成</li>
* <li>实现 {@link IRValue} 接口可作为 IRInstruction 的操作数</li>
* <li>具备良好的打印与调试格式%id</li>
* </ul>
*
* 适用于表达式求值参数传递函数返回值临时变量等所有中间值场景
*
* @param id 寄存器的唯一编号通常从 0 开始递增
*/
public record IRVirtualRegister(int id) implements IRValue {
/**
* 将虚拟寄存器转换为字符串格式方便输出和调试
* 格式为%<id>例如 %3 表示编号为 3 的虚拟寄存器
*
* @return 格式化的字符串表示
*/
@Override
public String toString() {
return "%" + id;
}
}

View File

@ -0,0 +1,46 @@
package org.jcnc.snow.compiler.lexer.base;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
import java.util.List;
/**
* {@code TokenScanner} 接口定义了所有词法扫描器的统一行为规范
* <p>
* 编译器前端中的词法分析阶段将源代码字符流解析为语义上有意义的记号token
* 每种类型的记号如标识符数字字符串符号等应有对应的 {@code TokenScanner} 实现类
* 词法分析器根据当前输入字符判断并分派给能处理该字符的扫描器进行处理
* </p>
* <p>
* 实现类通常会结合 {@link LexerContext} 提供的流访问与状态接口
* 完成一个完整 Token 的提取并将其添加到结果集中
* </p>
*/
public interface TokenScanner {
/**
* 判断当前字符是否可以由该扫描器处理
* <p>
* 词法分析器会按顺序查询已注册的 {@code TokenScanner} 实例
* 使用该方法决定当前字符是否可由某个扫描器识别与处理
* </p>
*
* @param c 当前读取的字符
* @param ctx 当前词法分析上下文提供字符流和辅助状态
* @return 若该扫描器可处理当前字符则返回 {@code true}否则返回 {@code false}
*/
boolean canHandle(char c, LexerContext ctx);
/**
* 处理以当前字符为起始的 token并将扫描结果添加至 tokens 列表中
* <p>
* 扫描器需消费一定数量的字符构建合法的 {@link Token} 实例
* 并调用 {@code tokens.add(...)} 添加至结果集中
* </p>
*
* @param ctx 当前词法上下文
* @param tokens 存储扫描结果的 token 列表
*/
void handle(LexerContext ctx, List<Token> tokens);
}

View File

@ -0,0 +1,125 @@
package org.jcnc.snow.compiler.lexer.core;
import org.jcnc.snow.compiler.lexer.base.TokenScanner;
/**
* {@code LexerContext} 是词法分析阶段的上下文状态管理器
* <p>
* 该类提供对源代码字符流的读取访问追踪当前行号与列号
* 并支持字符匹配回看与指针推进等操作 {@link TokenScanner} 实现进行词法识别的重要支撑工具
* </p>
* <p>
* 所有源代码输入在构造时统一将 Windows 风格的换行符\r\n转换为 Unix 风格\n
* 保证换行行为一致性
* </p>
*/
public class LexerContext {
/** 源代码字符串,换行符已标准化为 \n */
private final String source;
/** 当前扫描位置(从 0 开始的偏移) */
private int pos = 0;
/** 当前行号,从 1 开始 */
private int line = 1;
/** 当前列号,从 1 开始 */
private int col = 1;
/** 上一个字符对应的列号(用于位置精确记录) */
private int lastCol = 1;
/**
* 构造一个新的 {@code LexerContext} 实例并标准化换行符
*
* @param source 原始源代码字符串
*/
public LexerContext(String source) {
this.source = source.replace("\r\n", "\n");
}
/**
* 判断是否已读取到源代码末尾
*
* @return 若已结束返回 {@code true}否则返回 {@code false}
*/
public boolean isAtEnd() {
return pos >= source.length();
}
/**
* 消费当前字符并前进一个位置自动更新行列信息
*
* @return 当前字符若已结束则返回空字符'\0'
*/
public char advance() {
if (isAtEnd()) return '\0';
char c = source.charAt(pos++);
lastCol = col;
if (c == '\n') {
line++;
col = 1;
} else {
col++;
}
return c;
}
/**
* 查看当前位置的字符但不前进
*
* @return 当前字符若结束则返回空字符
*/
public char peek() {
return isAtEnd() ? '\0' : source.charAt(pos);
}
/**
* 查看下一个字符但不改变位置
*
* @return 下一个字符若结束则返回空字符
*/
public char peekNext() {
return pos + 1 >= source.length() ? '\0' : source.charAt(pos + 1);
}
/**
* 若当前字符与期望字符相同则前进并返回 {@code true}否则不动并返回 {@code false}
*
* @param expected 期待匹配的字符
* @return 是否匹配成功并消费
*/
public boolean match(char expected) {
if (isAtEnd() || source.charAt(pos) != expected) return false;
advance();
return true;
}
/**
* 获取当前位置的行号
*
* @return 当前行号 1 开始
*/
public int getLine() {
return line;
}
/**
* 获取当前位置的列号
*
* @return 当前列号 1 开始
*/
public int getCol() {
return col;
}
/**
* 获取上一个字符所在的列号
*
* @return 上一个字符对应的列位置
*/
public int getLastCol() {
return lastCol;
}
}

View File

@ -0,0 +1,111 @@
package org.jcnc.snow.compiler.lexer.core;
import org.jcnc.snow.compiler.lexer.base.TokenScanner;
import org.jcnc.snow.compiler.lexer.scanners.*;
import org.jcnc.snow.compiler.lexer.token.Token;
import java.util.ArrayList;
import java.util.List;
/**
* {@code LexerEngine} 是编译器前端的词法分析器核心实现
* <p>
* 负责将源代码字符串按顺序扫描并转换为一系列 {@link Token} 实例
* 每个 Token 表示语法上可识别的最小单位如标识符关键字常量运算符等
* <p>
* 分析流程通过注册多个 {@link TokenScanner} 扫描器实现类型识别
* 并由 {@link LexerContext} 提供字符流与位置信息支持
* 支持文件名传递遇到非法字符时会以文件名:::错误信息输出简洁诊断
* </p>
*/
public class LexerEngine {
/**
* 扫描生成的 Token 序列包含文件结束符 EOF
*/
private final List<Token> tokens = new ArrayList<>();
/**
* 词法上下文提供字符流读取与位置信息
*/
private final LexerContext context;
/**
* Token 扫描器集合按优先级顺序组织用于识别不同类别的 Token
*/
private final List<TokenScanner> scanners;
/**
* 构造词法分析器假定输入源自标准输入文件名默认为 <stdin>
*
* @param source 源代码文本
*/
public LexerEngine(String source) {
this(source, "<stdin>");
}
/**
* 构造词法分析器并指定源文件名用于诊断信息
* 构造时立即进行全量扫描
*
* @param source 源代码文本
* @param sourceName 文件名或来源描述"main.snow"
*/
public LexerEngine(String source, String sourceName) {
this.context = new LexerContext(source);
this.scanners = List.of(
new WhitespaceTokenScanner(), // 跳过空格制表符等
new NewlineTokenScanner(), // 处理换行符生成 NEWLINE Token
new CommentTokenScanner(), // 处理单行/多行注释
new NumberTokenScanner(), // 识别整数与浮点数字面量
new IdentifierTokenScanner(), // 识别标识符和关键字
new StringTokenScanner(), // 处理字符串常量
new OperatorTokenScanner(), // 识别运算符
new SymbolTokenScanner(), // 识别括号分号等符号
new UnknownTokenScanner() // 捕捉无法识别的字符最后兜底
);
// 主扫描流程遇到非法字符立即输出错误并终止进程
try {
scanAllTokens();
} catch (LexicalException le) {
// 输出文件名::: 错误信息简洁明了
System.err.printf(
"%s:%d:%d: %s%n",
sourceName,
le.getLine(), // 获取出错行号
le.getColumn(), // 获取出错列号
le.getMessage() // 错误描述
);
System.exit(65); // 65 = EX_DATAERR标准数据错误退出码
}
}
/**
* 主扫描循环将源代码转为 Token 序列
* 依次尝试每个扫描器直到找到可处理当前字符的扫描器为止
* 扫描到结尾后补充 EOF Token
*/
private void scanAllTokens() {
while (!context.isAtEnd()) {
char currentChar = context.peek();
// 依次查找能处理当前字符的扫描器
for (TokenScanner scanner : scanners) {
if (scanner.canHandle(currentChar, context)) {
scanner.handle(context, tokens);
break; // 已处理跳到下一个字符
}
}
}
// 末尾补一个 EOF 标记
tokens.add(Token.eof(context.getLine()));
}
/**
* 获取全部 Token包含 EOF返回只读列表
*
* @return 词法分析结果 Token 列表
*/
public List<Token> getAllTokens() {
return List.copyOf(tokens);
}
}

View File

@ -0,0 +1,54 @@
package org.jcnc.snow.compiler.lexer.core;
/**
* 词法异常LexicalException
* <p>
* {@link LexerEngine} 在扫描过程中遇到
* 非法或无法识别的字符序列时抛出该异常
* <ul>
* <li>异常消息仅包含一行简明错误信息包含行号与列号</li>
* <li>完全禁止 Java 堆栈信息输出使命令行输出保持整洁</li>
* </ul>
* <pre>
*
* main.s:2:19: Lexical error: Illegal character sequence '@' at 2:19
* </pre>
*/
public class LexicalException extends RuntimeException {
/** 错误发生的行号从1开始 */
private final int line;
/** 错误发生的列号从1开始 */
private final int column;
/**
* 构造词法异常
* @param reason 错误原因非法字符描述
* @param line 出错行号
* @param column 出错列号
*/
public LexicalException(String reason, int line, int column) {
// 构造出错消息并禁止异常堆栈打印
super(String.format("Lexical error: %s at %d:%d", reason, line, column),
null, false, false);
this.line = line;
this.column = column;
}
/**
* 屏蔽异常堆栈填充始终不打印堆栈信息
*/
@Override
public synchronized Throwable fillInStackTrace() { return this; }
/**
* 获取出错的行号
* @return 行号
*/
public int getLine() { return line; }
/**
* 获取出错的列号
* @return 列号
*/
public int getColumn() { return column; }
}

View File

@ -0,0 +1,38 @@
# Snow Compiler - Lexer 模块
> Snow 编译器的前端模块 —— 负责将源代码扫描并生成 Token 流
## 项目简介
**Lexer** 是 [Snow 编译器]() 项目的子模块,承担编译器前端的词法分析阶段任务。
它负责将源代码输入,根据 Snow 语言的语法规则,识别并划分为有意义的 Token 流为后续语法解析Parser提供标准输入。
该模块采用模块化设计,每类 Token 类型如标识符、运算符、数字、字符串等由独立的扫描器Scanner负责识别提高了扩展性和可维护性。
## 核心功能
- **词法分析引擎**:从源代码提取标准化 Token 流
- **模块化扫描器体系**:按需扩展不同类型的 Token 识别
- **灵活的上下文管理**:跟踪源代码位置,支持错误处理
- **统一的 Token 工厂**:集中创建 Token 实例
- **工具支持**Token 打印与调试辅助
## 模块结构
```
lexer/
├── base/ // 公共基础接口
├── core/ // 词法分析核心引擎与上下文
├── scanners/ // 各类 Token 扫描器
├── token/ // Token 定义、工厂与类型
├── utils/ // 辅助工具(如 Token 打印)
└── doc/ // 项目文档
```
## 开发环境
* JDK 23 或更高版本
* Maven 构建管理
* 推荐 IDEIntelliJ IDEA
---

View File

@ -0,0 +1,65 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.base.TokenScanner;
import org.jcnc.snow.compiler.lexer.token.Token;
import java.util.List;
import java.util.function.Predicate;
/**
* {@code AbstractTokenScanner} {@link TokenScanner} 的抽象实现
* 封装了常用的扫描行为与模板逻辑简化子类的实现负担
* <p>
* 子类只需实现 {@link #scanToken(LexerContext, int, int)} 方法
* 专注于处理具体的 Token 构造逻辑
* 而位置信息提取Token 添加等通用操作由本类统一完成
* </p>
*/
public abstract class AbstractTokenScanner implements TokenScanner {
/**
* 处理当前字符起始的 Token附带行列信息并加入 Token 列表
*
* @param ctx 当前词法分析上下文
* @param tokens 存储扫描结果的 Token 列表
*/
@Override
public void handle(LexerContext ctx, List<Token> tokens) {
int line = ctx.getLine();
int col = ctx.getCol();
Token token = scanToken(ctx, line, col);
if (token != null) {
tokens.add(token);
}
}
/**
* 抽象方法由子类实现具体的扫描逻辑
* <p>
* 实现应消费一定字符并根据规则构造 Token
* 若无需生成 Token可返回 null
* </p>
*
* @param ctx 当前扫描上下文
* @param line 当前行号
* @param col 当前列号
* @return 构造的 Token null
*/
protected abstract Token scanToken(LexerContext ctx, int line, int col);
/**
* 工具方法连续读取字符直到遇到不满足指定条件的字符
*
* @param ctx 当前词法上下文
* @param predicate 字符匹配条件
* @return 满足条件的字符组成的字符串
*/
protected String readWhile(LexerContext ctx, Predicate<Character> predicate) {
StringBuilder sb = new StringBuilder();
while (!ctx.isAtEnd() && predicate.test(ctx.peek())) {
sb.append(ctx.advance());
}
return sb.toString();
}
}

View File

@ -0,0 +1,74 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
* 注释扫描器处理源代码中的注释部分包括
* <ul>
* <li>单行注释 "//" 开头直到行尾</li>
* <li>多行注释 "/*" 开头 "*&#47;" 结尾</li>
* </ul>
* <p>
* 本扫描器会识别注释并生成 {@code TokenType.COMMENT} 类型的 Token
* 不会丢弃注释内容而是将完整注释文本保留在 Token 便于后续分析如文档提取保留注释等场景
* </p>
*/
public class CommentTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>当当前位置字符为 '/' 且下一个字符为 '/' '*' 表示可能是注释的起始</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果是注释的起始符则返回 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return c == '/' && (ctx.peekNext() == '/' || ctx.peekNext() == '*');
}
/**
* 实现注释的扫描逻辑
* <p>支持两种注释格式</p>
* <ul>
* <li><b>单行注释</b> "//" 开头直到遇到换行符</li>
* <li><b>多行注释</b> "/*" 开头直到遇到 "*&#47;" 结束</li>
* </ul>
*
* @param ctx 词法上下文
* @param line 当前行号用于 Token 位置信息
* @param col 当前列号用于 Token 位置信息
* @return 包含完整注释内容的 COMMENT 类型 Token
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
// 消费第一个 '/' 字符
ctx.advance();
StringBuilder sb = new StringBuilder("/");
// 处理单行注释 //
if (ctx.match('/')) {
sb.append('/');
while (!ctx.isAtEnd() && ctx.peek() != '\n') {
sb.append(ctx.advance());
}
}
// 处理多行注释 /* ... */
else if (ctx.match('*')) {
sb.append('*');
while (!ctx.isAtEnd()) {
char ch = ctx.advance();
sb.append(ch);
if (ch == '*' && ctx.peek() == '/') {
sb.append(ctx.advance()); // 消费 '/'
break;
}
}
}
return new Token(TokenType.COMMENT, sb.toString(), line, col);
}
}

View File

@ -0,0 +1,48 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenFactory;
/**
* 标识符扫描器处理标识符的识别如变量名函数名等
* <p>
* 识别规则如下
* <ul>
* <li>必须以字母或下划线_开头</li>
* <li>后续字符可以是字母数字或下划线</li>
* </ul>
* <p>
* 扫描完成后会调用 {@link TokenFactory} 自动判断是否为关键字
* 并返回对应类型的 {@link Token}
*/
public class IdentifierTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>如果字符为字母或下划线则认为是标识符的起始</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果是标识符起始字符则返回 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return Character.isLetter(c) || c == '_';
}
/**
* 执行标识符的扫描逻辑
* <p>连续读取满足标识符规则的字符序列交由 {@code TokenFactory} 创建对应的 Token</p>
*
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 标识符或关键字类型的 Token
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
String lexeme = readWhile(ctx, ch -> Character.isLetterOrDigit(ch) || ch == '_');
return TokenFactory.create(lexeme, line, col);
}
}

View File

@ -0,0 +1,41 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
* 换行符扫描器将源代码中的换行符\n识别为 {@code NEWLINE} 类型的 Token
* <p>
* 通常用于记录行的分界辅助语法分析阶段进行行敏感的判断或保持结构清晰
*/
public class NewlineTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>当字符为换行符\n时返回 true</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果为换行符则返回 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return c == '\n';
}
/**
* 执行换行符的扫描逻辑
* <p>读取一个换行符并生成对应的 {@code NEWLINE} 类型 Token</p>
*
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 表示换行的 Token
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
ctx.advance();
return new Token(TokenType.NEWLINE, "\n", line, col);
}
}

View File

@ -0,0 +1,92 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
* 数字扫描器识别整数小数以及带有<strong>类型后缀</strong>的数字字面量
* <p>
* 支持的格式示例
* <ul>
* <li>整数123045678</li>
* <li>小数3.140.512.0</li>
* <li>带类型后缀2.0f42L7s255B</li>
* </ul>
* <p>
* 语法允许在数字 (整数或小数) 末尾添加以下<strong>单字符后缀</strong>来显式指定常量类型
* <pre>b | s | l | f | d // 分别对应 byteshortlongfloatdouble
* B | S | L | F | D // 同上大小写皆可</pre>
* 生成的 Token 类型始终为 {@code NUMBER_LITERAL}词法单元将携带完整的文本含后缀若存在
*/
public class NumberTokenScanner extends AbstractTokenScanner {
/**
* 可选类型后缀字符集合 (大小写均可)
* {@code ExpressionBuilder} 内的后缀解析逻辑保持一致
*/
private static final String SUFFIX_CHARS = "bslfdBSLFD";
/**
* 判断是否可以处理当前位置的字符
* <p>当字符为数字时表示可能是数字字面量的起始</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果为数字字符则返回 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return Character.isDigit(c);
}
/**
* 执行数字扫描逻辑
* <ol>
* <li>连续读取数字字符允许出现<strong>一个</strong>小数点用于识别整数或小数</li>
* <li>读取完主体后<strong>一次性</strong>检查下一个字符若属于合法类型后缀则吸收</li>
* </ol>
* 这样可以保证诸如 {@code 2.0f} 被视为一个整体的 {@code NUMBER_LITERAL}
* 而不是拆分成 "2.0" "f" 两个 Token
*
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 表示数字字面量的 Token
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
StringBuilder sb = new StringBuilder();
boolean hasDot = false; // 标识是否已经遇到过小数点
/*
* 1 扫描整数或小数主体
* 允许出现一个小数点其余必须是数字
*/
while (!ctx.isAtEnd()) {
char c = ctx.peek();
if (c == '.' && !hasDot) {
hasDot = true;
sb.append(ctx.advance());
} else if (Character.isDigit(c)) {
sb.append(ctx.advance());
} else {
break; // 遇到非数字/第二个点 => 主体结束
}
}
/*
* 2 可选类型后缀
* 如果下一字符是合法后缀字母则一起纳入当前 Token
*/
if (!ctx.isAtEnd()) {
char suffix = ctx.peek();
if (SUFFIX_CHARS.indexOf(suffix) >= 0) {
sb.append(ctx.advance());
}
}
// 构造并返回 NUMBER_LITERAL Token文本内容形如 "123", "3.14f"
return new Token(TokenType.NUMBER_LITERAL, sb.toString(), line, col);
}
}

View File

@ -0,0 +1,116 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
* 运算符扫描器识别逻辑与比较运算符包括单字符和双字符组合
* <p>
* 支持的运算符包括
* <ul>
* <li>赋值与比较===!=</li>
* <li>关系运算符&gt;&gt;=&lt;&lt;=</li>
* <li>逻辑运算符&&||</li>
* </ul>
* <p>
* 不符合上述组合的字符会返回 {@code UNKNOWN} 类型的 Token
*/
public class OperatorTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>运算符扫描器关注的起始字符包括=!&lt;&gt;|&amp;</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果是潜在的运算符起始字符则返回 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return "=!<>|&%".indexOf(c) >= 0;
}
/**
* 扫描并识别运算符 Token
* <p>支持组合运算符判断 ==!=&gt;=
* 若无法匹配组合形式则退回单字符形式</p>
*
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 对应的运算符 Token无法识别的运算符返回 {@code UNKNOWN}
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
char c = ctx.advance();
String lexeme;
TokenType type;
switch (c) {
case '=':
if (ctx.match('=')) {
lexeme = "==";
type = TokenType.DOUBLE_EQUALS;
} else {
lexeme = "=";
type = TokenType.EQUALS;
}
break;
case '!':
if (ctx.match('=')) {
lexeme = "!=";
type = TokenType.NOT_EQUALS;
} else {
lexeme = "!";
type = TokenType.UNKNOWN;
}
break;
case '>':
if (ctx.match('=')) {
lexeme = ">=";
type = TokenType.GREATER_EQUAL;
} else {
lexeme = ">";
type = TokenType.GREATER_THAN;
}
break;
case '<':
if (ctx.match('=')) {
lexeme = "<=";
type = TokenType.LESS_EQUAL;
} else {
lexeme = "<";
type = TokenType.LESS_THAN;
}
break;
case '%':
lexeme = "%";
type = TokenType.MODULO;
break;
case '&':
if (ctx.match('&')) {
lexeme = "&&";
type = TokenType.AND;
} else {
lexeme = "&";
type = TokenType.UNKNOWN;
}
break;
case '|':
if (ctx.match('|')) {
lexeme = "||";
type = TokenType.OR;
} else {
lexeme = "|";
type = TokenType.UNKNOWN;
}
break;
default:
lexeme = String.valueOf(c);
type = TokenType.UNKNOWN;
}
return new Token(type, lexeme, line, col);
}
}

View File

@ -0,0 +1,63 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
* 字符串扫描器处理双引号包裹的字符串字面量支持基本的转义字符
* <p>
* 支持格式示例
* <ul>
* <li>"hello"</li>
* <li>"line\\nbreak"</li>
* <li>"escaped \\\" quote"</li>
* </ul>
* <p>
* 扫描器会保留原始字符串的形式包含双引号和转义符
* 并生成 {@code STRING_LITERAL} 类型的 Token
*/
public class StringTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>当字符为双引号")时,认为是字符串字面量的开始。</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果为字符串起始符则返回 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return c == '"';
}
/**
* 执行字符串的扫描逻辑
* <p>从当前位置开始读取直到匹配结束的双引号
* 支持转义字符 \"\\n 等),不会中断字符串扫描。</p>
*
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 字符串字面量类型的 Token
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
StringBuilder sb = new StringBuilder();
sb.append(ctx.advance()); // 起始双引号
while (!ctx.isAtEnd()) {
char c = ctx.advance();
sb.append(c);
if (c == '\\') {
sb.append(ctx.advance()); // 添加转义字符后的实际字符
} else if (c == '"') {
break;
}
}
return new Token(TokenType.STRING_LITERAL, sb.toString(), line, col);
}
}

View File

@ -0,0 +1,60 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
* 符号扫描器识别常见的单字符符号如冒号逗号括号和算术符号
* <p>
* 支持的符号包括
* <ul>
* <li>标点符号: , .</li>
* <li>括号( )</li>
* <li>算术运算符+ - *</li>
* </ul>
* <p>
* 生成的 Token 类型根据字符分别对应 {@link TokenType} 枚举中的定义
*/
public class SymbolTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>本扫描器处理的符号包括 : , ( ) . + - *</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果是支持的符号字符则返回 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return ":,().+-*/".indexOf(c) >= 0;
}
/**
* 执行符号的扫描逻辑
* <p>根据字符匹配对应的 {@code TokenType} 类型构造并返回 Token</p>
*
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 表示符号的 Token
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
char c = ctx.advance();
TokenType type = switch (c) {
case ':' -> TokenType.COLON;
case ',' -> TokenType.COMMA;
case '.' -> TokenType.DOT;
case '+' -> TokenType.PLUS;
case '-' -> TokenType.MINUS;
case '*' -> TokenType.MULTIPLY;
case '/' -> TokenType.DIVIDE;
case '(' -> TokenType.LPAREN;
case ')' -> TokenType.RPAREN;
default -> TokenType.UNKNOWN;
};
return new Token(type, String.valueOf(c), line, col);
}
}

View File

@ -0,0 +1,55 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.core.LexicalException;
import org.jcnc.snow.compiler.lexer.token.Token;
/**
* 未知 Token 扫描器UnknownTokenScanner
* <p>
* 作为所有扫描器的兜底处理器当前字符若不被任何其他扫描器识别
* 由本类处理并抛出 {@link LexicalException}终止词法分析流程
* </p>
* <p>
* 主要作用保证所有非法不可识别的字符@$等不会被静默跳过或误当作合法 Token
* 而是在词法阶段立刻定位并报错有助于尽早发现源代码问题
* </p>
*/
public class UnknownTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前字符
* 对于 UnknownTokenScanner始终返回 true兜底扫描器必须排在扫描器链末尾
* @param c 当前待处理字符
* @param ctx 词法上下文
* @return 是否处理该字符始终为 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return true;
}
/**
* 实际处理非法字符序列的方法
* 连续读取所有无法被其他扫描器识别的字符组成错误片段并抛出异常
* @param ctx 词法上下文
* @param line 错误发生行号
* @param col 错误发生列号
* @return 不会返回Token始终抛异常
* @throws LexicalException 非法字符导致的词法错误
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
// 读取一段非法字符既不是字母数字也不是常见符号
String lexeme = readWhile(ctx, ch ->
!Character.isLetterOrDigit(ch) &&
!Character.isWhitespace(ch) &&
":,().+-*{};\"".indexOf(ch) < 0
);
// 如果没读到任何字符则把当前字符单独作为非法片段
if (lexeme.isEmpty())
lexeme = String.valueOf(ctx.advance());
// 抛出词法异常并带上错误片段与具体位置
throw new LexicalException("Illegal character sequence '" + lexeme + "'", line, col);
}
}

View File

@ -0,0 +1,42 @@
package org.jcnc.snow.compiler.lexer.scanners;
import org.jcnc.snow.compiler.lexer.core.LexerContext;
import org.jcnc.snow.compiler.lexer.token.Token;
/**
* 空白符扫描器跳过非换行的空白字符不生成任何 Token
* <p>
* 支持的空白字符包括空格制表符Tab但不包括换行符 {@link NewlineTokenScanner} 处理
* <p>
* 此扫描器仅用于忽略多余的空白内容保持 Token 流的紧凑性
*/
public class WhitespaceTokenScanner extends AbstractTokenScanner {
/**
* 判断是否可以处理当前位置的字符
* <p>当字符为空白符但不是换行符\n时返回 true</p>
*
* @param c 当前字符
* @param ctx 当前词法上下文
* @return 如果为非换行的空白字符则返回 true
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
return Character.isWhitespace(c) && c != '\n';
}
/**
* 跳过空白字符不生成 Token
* <p>直接推进上下文位置返回 null</p>
*
* @param ctx 词法上下文
* @param line 当前行号
* @param col 当前列号
* @return 始终返回 null表示无 Token 产生
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
ctx.advance();
return null;
}
}

View File

@ -0,0 +1,113 @@
package org.jcnc.snow.compiler.lexer.token;
/**
* {@code Token} 表示词法分析过程中生成的最小语法单元
* 包含类型信息词素内容源代码中对应的原始文本片段以及精确的位置信息
* <p>
* 一个 Token 通常对应源代码中一个具有语义意义的片段如关键字标识符常量运算符等
* 区分 lexeme清洗后的词素 raw原始片段是为了支持如带引号的字符串注释等需要保留原始形式的元素
* </p>
*/
public class Token {
/** Token 的类型,如 KEYWORD、IDENTIFIER、TYPE 等。 */
private final TokenType type;
/** 清洗后的词素内容,例如去掉引号的字符串正文或注释正文。 */
private final String lexeme;
/** 源代码中对应的原始片段,可能包含引号、注释符号等。 */
private final String raw;
/** Token 在源文件中的行号,从 1 开始计数。 */
private final int line;
/** Token 在源文件行中的列号,从 1 开始计数。 */
private final int col;
/**
* 构造一个完整信息的 Token 实例
*
* @param type Token 类型
* @param lexeme 清洗后的词素内容
* @param raw 源代码中的原始片段
* @param line 所在源文件的行号 1 开始
* @param col 所在源文件行的列号 1 开始
*/
public Token(TokenType type, String lexeme, String raw, int line, int col) {
this.type = type;
this.lexeme = lexeme;
this.raw = raw;
this.line = line;
this.col = col;
}
/**
* 构造一个简化形式的 Token词素与原始片段一致
* <p>
* 适用于标识符关键字符号等不需要区分原始与清洗内容的 Token
* </p>
*
* @param type Token 类型
* @param lexeme Token 内容同时作为原始片段
* @param line 行号
* @param col 列号
*/
public Token(TokenType type, String lexeme, int line, int col) {
this(type, lexeme, lexeme, line, col);
}
/**
* 构造并返回一个表示文件结束EOF Token 实例
* <p>
* 用于表示扫描结束的特殊符号
* </p>
*
* @param line 文件结束所在行号
* @return EOF 类型的 Token
*/
public static Token eof(int line) {
return new Token(TokenType.EOF, "", "", line, 1);
}
/** @return 此 Token 的类型 */
public TokenType getType() {
return type;
}
/** @return 清洗后的词素内容lexeme */
public String getLexeme() {
return lexeme;
}
/** @return 源代码中的原始片段 */
public String getRaw() {
return raw;
}
/** @return Token 所在的源文件行号(从 1 开始) */
public int getLine() {
return line;
}
/** @return Token 所在行的列号(从 1 开始) */
public int getCol() {
return col;
}
/**
* 返回该 Token 的字符串表示包含类型词素行列信息
* <p>
* 通常用于日志打印或调试目的
* </p>
*
* @return Token 的描述性字符串
*/
@Override
public String toString() {
return String.format(
"Token(type=%s, lexeme='%s', line=%d, col=%d)",
type, lexeme, line, col
);
}
}

View File

@ -0,0 +1,112 @@
package org.jcnc.snow.compiler.lexer.token;
import java.util.Set;
/**
* {@code TokenFactory} 是一个用于词法分析的静态工厂类
* <p>
* 该类提供静态方法根据输入的原始词素字符串
* 自动识别其对应的词法类型并生成相应的 {@link Token} 实例
* 支持自动区分语言关键字内置类型合法标识符等
* 简化了词法扫描器Lexer的核心处理流程
* </p>
*
* <p>
* 主要功能与特性
* <ul>
* <li>统一管理语言关键字和类型名集合便于扩展与维护</li>
* <li>自动推断 Token 类型无需外部干预</li>
* <li>对不合法的词素自动标记为 UNKNOWN 类型</li>
* </ul>
* </p>
*
* @author 你的名字
* @version 1.0
*/
public class TokenFactory {
/**
* 语言的保留关键字集合
*/
private static final Set<String> KEYWORDS = Set.of(
"module", "function", "parameter", "return_type",
"body", "end", "if", "then", "else", "loop",
"declare", "return", "import",
"initializer", "condition", "update"
);
/**
* 内置类型名称集合 intstring
*/
private static final Set<String> TYPES = Set.of(
"int", "string", "float", "bool", "void", "double", "long", "short", "byte"
);
/**
* 创建一个根据内容自动推断类型的 {@link Token} 实例
* <p>
* 优先级顺序为类型TYPE &gt; 关键字KEYWORD &gt; 标识符IDENTIFIER &gt; 未知UNKNOWN
* 若原始字符串同时属于多类则按优先级最高者处理
* </p>
*
* @param raw 原始词法单元文本
* @param line Token 所在行号从1开始
* @param col Token 所在列号从1开始
* @return 构造好的 {@link Token} 实例
*/
public static Token create(String raw, int line, int col) {
TokenType type = determineTokenType(raw);
return new Token(type, raw, line, col);
}
/**
* 判断并推断给定字符串的 {@link TokenType} 类型
* <p>
* 优先级依次为TYPE &gt; KEYWORD &gt; IDENTIFIER &gt; UNKNOWN
* </p>
*
* @param raw 原始词素字符串
* @return 推断出的 {@link TokenType} 类型
*/
private static TokenType determineTokenType(String raw) {
if (isType(raw)) return TokenType.TYPE;
if (isKeyword(raw)) return TokenType.KEYWORD;
if (isIdentifier(raw)) return TokenType.IDENTIFIER;
return TokenType.UNKNOWN;
}
/**
* 判断指定字符串是否为内置类型标识
*
* @param raw 输入的字符串
* @return 若为类型名则返回 {@code true}否则返回 {@code false}
*/
private static boolean isType(String raw) {
return TYPES.contains(raw);
}
/**
* 判断指定字符串是否为语言保留关键字
*
* @param raw 输入的字符串
* @return 若为关键字则返回 {@code true}否则返回 {@code false}
*/
private static boolean isKeyword(String raw) {
return KEYWORDS.contains(raw);
}
/**
* 判断指定字符串是否为合法的标识符Identifier
* <p>
* 合法标识符需以字母a-z/A-Z或下划线_开头
* 后续可包含字母数字或下划线
* 例如_abc, a1b2, name_123 均为合法标识符
* </p>
*
* @param raw 输入的字符串
* @return 若为合法标识符则返回 {@code true}否则返回 {@code false}
*/
private static boolean isIdentifier(String raw) {
return raw.matches("[a-zA-Z_][a-zA-Z0-9_]*");
}
}

View File

@ -0,0 +1,95 @@
package org.jcnc.snow.compiler.lexer.token;
/**
* {@code TokenType} 枚举定义了 Snow 编程语言词法分析阶段可识别的所有词法单元类型
* <p>
* 每个枚举值代表一种语义类别
* 用于描述源代码中出现的关键字标识符字面量运算符控制符号等语言构件
* 在语法分析及后续处理阶段依赖该枚举对 Token 进行分类判断与分支处理
* </p>
*/
public enum TokenType {
/** 普通标识符,如变量名、函数名等 */
IDENTIFIER,
/** 语言保留关键字(如 if、return、module 等) */
KEYWORD,
/** 内置类型名称(如 int、string、bool 等) */
TYPE,
/** 字符串字面量(如 "hello" */
STRING_LITERAL,
/** 数字字面量(整数或浮点数) */
NUMBER_LITERAL,
/** 冒号 ':' */
COLON,
/** 逗号 ',' */
COMMA,
/** 点号 '.' */
DOT,
/** 赋值符号 '=' */
EQUALS,
/** 取模运算符 '%' */
MODULO,
/** 加号 '+' */
PLUS,
/** 乘号 '*' */
MULTIPLY,
/**除号 '/' */
DIVIDE,
/** 减号 '-' */
MINUS,
/** 左括号 '(' */
LPAREN,
/** 右括号 ')' */
RPAREN,
/** 相等比较符号 '==' */
DOUBLE_EQUALS,
/** 不等比较符号 '!=' */
NOT_EQUALS,
/** 大于符号 '>' */
GREATER_THAN,
/** 大于等于符号 '>=' */
GREATER_EQUAL,
/** 小于符号 '<' */
LESS_THAN,
/** 小于等于符号 '<=' */
LESS_EQUAL,
/** 逻辑与符号 '&&' */
AND,
/** 逻辑或符号 '||' */
OR,
/** 换行符,标识逻辑换行 */
NEWLINE,
/** 单行或多行注释内容 */
COMMENT,
/** 文件结束符End of File */
EOF,
/** 无法识别的非法或未知符号 */
UNKNOWN
}

View File

@ -0,0 +1,63 @@
package org.jcnc.snow.compiler.lexer.utils;
import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
import java.util.List;
/**
* TokenPrinter 用于以表格形式将词法分析生成的 Token 列表输出到控制台
* <p>
* 输出包含每个 Token 的行号列号类型以及词素lexeme
* 并对换行制表符等特殊字符进行转义显示
* </p>
*/
public class TokenPrinter {
/**
* 将给定的 Token 列表打印到标准输出控制台
* <p>
* 输出格式
* <pre>
* line col type lexeme
* ----------------------------------------------------
* 1 1 KEYWORD module
* 1 7 IDENTIFIER MyModule
* ...
* </pre>
* 并且对 lexeme 中的换行符制表符回车符等进行转义\n\t\r
* 以便在表格中保持排版整齐如果遇到类型为 {@link TokenType#NEWLINE} Token
* 会在该行后额外插入一个空行以增强可读性
*
* @param tokens 要打印的 Token 列表不应为 null列表中的每个元素
* 都应包含有效的行号列号类型和词素信息
*/
public static void print(List<Token> tokens) {
// 打印表头列名对齐宽度分别为 6616
System.out.printf("%-6s %-6s %-16s %s%n", "line", "col", "type", "lexeme");
System.out.println("----------------------------------------------------");
// 逐个 Token 输出对应信息
for (Token token : tokens) {
// lexeme 中的特殊字符进行转义避免表格错位
String lexeme = token.getLexeme()
.replace("\n", "\\n")
.replace("\t", "\\t")
.replace("\r", "\\r");
// 按照固定格式输出行号列号类型词素
System.out.printf("%-6d %-6d %-16s %s%n",
token.getLine(),
token.getCol(),
token.getType(),
lexeme
);
// 如果当前 Token 是换行符类型则额外打印一行空白行
if (token.getType() == TokenType.NEWLINE) {
System.out.println();
}
}
}
}

View File

@ -0,0 +1,34 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
/**
* {@code AssignmentNode} 表示抽象语法树AST中的赋值语句节点
* <p>
* 赋值语句用于将右侧表达式的值存储到左侧指定的变量中
* 通常形式为 {@code x = expression}其中 {@code x} 是目标变量
* {@code expression} 是用于计算赋值结果的表达式
* </p>
* <p>
* 该节点作为语句节点的一种实现适用于语义分析类型检查IR 构建等多个阶段
* </p>
*
* @param variable 左值变量名即赋值目标
* @param value 表达式右值即赋值来源
*/
public record AssignmentNode(String variable, ExpressionNode value) implements StatementNode {
/**
* 返回赋值语句的字符串形式便于调试与日志输出
* <p>
* 典型格式形如 {@code x = y + 1}
* </p>
*
* @return 表示赋值语句的字符串形式
*/
@Override
public String toString() {
return variable + " = " + value;
}
}

View File

@ -0,0 +1,32 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/**
* {@code BinaryExpressionNode} 表示抽象语法树AST中的二元运算表达式节点
* <p>
* 二元表达式通常由两个操作数和一个中间操作符构成例如 {@code a + b}
* 此结构广泛用于数学计算逻辑判断字符串拼接等语法结构中
* </p>
*
* @param left 左操作数子表达式
* @param operator 运算符字符串 "+", "-", "*", "/"
* @param right 右操作数子表达式
*/
public record BinaryExpressionNode(ExpressionNode left, String operator,
ExpressionNode right) implements ExpressionNode {
/**
* 返回该二元运算表达式的字符串表示形式
* <p>
* 输出格式为{@code left + " " + operator + " " + right}
* 适用于调试或打印语法树结构
* </p>
*
* @return 表示该二元表达式的字符串
*/
@Override
public String toString() {
return left + " " + operator + " " + right;
}
}

View File

@ -0,0 +1,39 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import java.util.List;
/**
* {@code CallExpressionNode} 表示抽象语法树AST中的函数调用表达式节点
* <p>
* 函数调用表达式用于表示函数或过程的调用操作
* 包括被调用对象callee以及一组参数表达式arguments
* </p>
*
* @param callee 被调用的表达式节点通常为函数标识符或成员访问表达式
* @param arguments 参数表达式列表依照调用顺序排列
*/
public record CallExpressionNode(ExpressionNode callee, List<ExpressionNode> arguments) implements ExpressionNode {
/**
* 返回函数调用表达式的字符串形式
* <p>
* 该格式将输出为类似 {@code foo(a, b, c)} 的形式
* 便于调试与语法树可视化
* </p>
*
* @return 表示函数调用的字符串表示
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(callee).append("(");
for (int i = 0; i < arguments.size(); i++) {
sb.append(arguments.get(i));
if (i + 1 < arguments.size()) sb.append(", ");
}
sb.append(")");
return sb.toString();
}
}

View File

@ -0,0 +1,65 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
import java.util.Optional;
/**
* {@code DeclarationNode} 表示抽象语法树AST中的变量声明语句节点
* <p>
* 变量声明用于在语法层引入新的标识符及其类型信息
* 通常格式为 {@code type name = initializer;}其中初始化表达式可省略
* </p>
*/
public class DeclarationNode implements StatementNode {
/** 声明的变量名称 */
private final String name;
/** 变量的数据类型(如 "int", "string" */
private final String type;
/** 可选的初始化表达式 */
private final Optional<ExpressionNode> initializer;
/**
* 构造一个 {@code DeclarationNode} 实例
*
* @param name 变量名称
* @param type 变量类型字符串 "int""string"
* @param initializer 可选初始化表达式若为 {@code null} 表示未初始化
*/
public DeclarationNode(String name, String type, ExpressionNode initializer) {
this.name = name;
this.type = type;
this.initializer = Optional.ofNullable(initializer);
}
/**
* 获取变量名称
*
* @return 变量名字符串
*/
public String getName() {
return name;
}
/**
* 获取变量类型字符串
*
* @return 类型名称 "int"
*/
public String getType() {
return type;
}
/**
* 获取可选的初始化表达式
*
* @return 一个 Optional 包装的初始化表达式对象可能为空
*/
public Optional<ExpressionNode> getInitializer() {
return initializer;
}
}

View File

@ -0,0 +1,16 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
/**
* {@code ExpressionStatementNode} 表示抽象语法树AST中的表达式语句节点
* <p>
* 表达式语句通常由一个单独的表达式组成并以语句形式出现
* 例如{@code foo();}{@code x = 1;}{@code print("hello");}
* </p>
*
* @param expression 表达式主体通常为函数调用赋值方法链式调用等可求值表达式
*/
public record ExpressionStatementNode(ExpressionNode expression) implements StatementNode {
}

View File

@ -0,0 +1,23 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.Node;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
import java.util.List;
/**
* {@code FunctionNode} 表示抽象语法树AST中的函数定义结构
* <p>
* 函数定义通常包含函数名形参列表返回类型以及函数体
* 在语义分析类型检查与代码生成等阶段具有核心地位
* 示例{@code int add(int a, int b) { return a + b; }}
* </p>
*
* @param name 函数名称标识符
* @param parameters 参数列表每项为 {@link ParameterNode} 表示一个形参定义
* @param returnType 函数的返回类型 "int""void"
* @param body 函数体语句块由一组 {@link StatementNode} 构成
*/
public record FunctionNode(String name, List<ParameterNode> parameters, String returnType,
List<StatementNode> body) implements Node {
}

View File

@ -0,0 +1,25 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/**
* {@code IdentifierNode} 表示抽象语法树AST中的标识符表达式节点
* <p>
* 该节点用于表示变量名函数名字段名等符号引用
* 在语义分析中通常需要将此类节点绑定到其声明位置或符号表项
* </p>
*
* @param name 标识符的文本名称如变量名 "x"函数名 "foo"
*/
public record IdentifierNode(String name) implements ExpressionNode {
/**
* 返回标识符节点的字符串形式通常为其名称本身
*
* @return 标识符名称字符串
*/
@Override
public String toString() {
return name;
}
}

View File

@ -0,0 +1,38 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
import java.util.List;
/**
* {@code IfNode} 表示抽象语法树AST中的条件语句结构if-else
* <p>
* 该节点包含一个条件表达式condition一个 then 分支语句列表
* 以及一个可选的 else 分支语句列表
* </p>
* <p>
* 条件表达式为布尔类型决定是否执行 then 分支
* condition 为假则执行 else 分支如果提供
* </p>
* <p>
* 示例语法结构
* </p>
* <pre>{@code
* if (x > 0) {
* print("Positive");
* } else {
* print("Negative");
* }
* }</pre>
*
* @param condition 控制分支执行的条件表达式
* @param thenBranch 条件为 true 时执行的语句块
* @param elseBranch 条件为 false 时执行的语句块可为空
*/
public record IfNode(
ExpressionNode condition,
List<StatementNode> thenBranch,
List<StatementNode> elseBranch
) implements StatementNode {
}

View File

@ -0,0 +1,19 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.Node;
/**
* {@code ImportNode} 表示抽象语法树AST中的 import 语句节点
* <p>
* import 语句用于引入外部模块或库文件其语法形式一般为
* {@code import my.module;}
* </p>
* <p>
* 本节点仅存储导入目标模块的名称不包含路径解析或绑定逻辑
* 这些通常由语义分析器或模块加载器处理
* </p>
*
* @param moduleName 被导入的模块名称通常为点分层次结构 "core.utils"
*/
public record ImportNode(String moduleName) implements Node {
}

View File

@ -0,0 +1,23 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
import java.util.List;
/**
* {@code LoopNode} 表示抽象语法树AST中的循环语句结构
* <p>
* 该节点建模了类似传统 {@code for} 循环的控制结构
* 包含初始化语句循环条件更新语句及循环体
* 每一部分均对应为 AST 中的子节点便于进一步语义分析与代码生成
* </p>
*
* @param initializer 在循环开始前执行的初始化语句
* @param condition 每次迭代前评估的条件表达式控制循环是否继续
* @param update 每轮迭代完成后执行的更新语句
* @param body 循环体语句列表表示循环主体执行逻辑
*/
public record LoopNode(StatementNode initializer, ExpressionNode condition, StatementNode update,
List<StatementNode> body) implements StatementNode {
}

View File

@ -0,0 +1,29 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/**
* {@code MemberExpressionNode} 表示抽象语法树AST中的成员访问表达式节点
* <p>
* 用于表示对象字段或方法的访问操作语法形式如 {@code object.member}
* 成员访问常见于结构体模块对象导入等上下文中是表达式链中常见的构件之一
* </p>
*
* @param object 左侧对象表达式表示成员所属的作用域或容器
* @param member 要访问的成员名称字段名或方法名
*/
public record MemberExpressionNode(ExpressionNode object, String member) implements ExpressionNode {
/**
* 返回成员访问表达式的字符串形式
* <p>
* 输出格式为 {@code object.member}用于调试或语法树可视化
* </p>
*
* @return 成员访问表达式的字符串形式
*/
@Override
public String toString() {
return object + "." + member;
}
}

View File

@ -0,0 +1,38 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.Node;
import java.util.List;
import java.util.StringJoiner;
/**
* 表示模块定义的 AST 节点
* 一个模块通常由模块名导入语句列表和函数定义列表组成
* }
*
* @param name 模块名称
* @param imports 模块导入列表每个导入是一个 {@link ImportNode}
* @param functions 模块中的函数列表每个函数是一个 {@link FunctionNode}
*/
public record ModuleNode(String name, List<ImportNode> imports, List<FunctionNode> functions) implements Node {
/**
* 返回模块节点的字符串表示形式包含模块名导入模块列表和函数列表
*
* @return 模块的简洁字符串表示用于调试和日志输出
*/
@Override
public String toString() {
StringJoiner importJoiner = new StringJoiner(", ");
for (ImportNode imp : imports) {
importJoiner.add(imp.moduleName());
}
StringJoiner funcJoiner = new StringJoiner(", ");
for (FunctionNode fn : functions) {
funcJoiner.add(fn.name());
}
return "Module(name=" + name
+ ", imports=[" + importJoiner + "]"
+ ", functions=[" + funcJoiner + "])";
}
}

View File

@ -0,0 +1,26 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/**
* {@code NumberLiteralNode} 表示抽象语法树AST中的数字字面量表达式节点
* <p>
* 用于表示源代码中的数值常量如整数 {@code 42} 或浮点数 {@code 3.14}
* 为了兼容不同数值格式本节点以字符串形式存储原始值
* 在语义分析或类型推导阶段再行解析为具体数值类型
* </p>
*
* @param value 数字字面量的原始字符串表示
*/
public record NumberLiteralNode(String value) implements ExpressionNode {
/**
* 返回数字字面量的字符串形式
*
* @return 字面量原始字符串值例如 "42" "3.14"
*/
@Override
public String toString() {
return value;
}
}

View File

@ -0,0 +1,29 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.Node;
/**
* {@code ParameterNode} 表示抽象语法树AST中的函数参数定义节点
* <p>
* 每个参数节点包含参数的名称和类型信息
* 用于构成函数签名并参与类型检查与函数调用匹配
* </p>
*
* @param name 参数名称标识符
* @param type 参数类型字符串 "int""string"
*/
public record ParameterNode(String name, String type) implements Node {
/**
* 返回参数的字符串形式格式为 {@code name:type}
* <p>
* 用于调试输出或构建函数签名描述
* </p>
*
* @return 参数的字符串形式 {@code count:int}
*/
@Override
public String toString() {
return name + ":" + type;
}
}

View File

@ -0,0 +1,43 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
import java.util.Optional;
/**
* {@code ReturnNode} 表示抽象语法树AST中的 return 语句节点
* <p>
* return 语句用于从当前函数中返回控制权并可携带一个可选的返回值表达式
* </p>
* <p>
* 示例
* <ul>
* <li>{@code return;}</li>
* <li>{@code return x + 1;}</li>
* </ul>
* </p>
*/
public class ReturnNode implements StatementNode {
/** 可选的返回值表达式 */
private final Optional<ExpressionNode> expression;
/**
* 构造一个 {@code ReturnNode} 实例
*
* @param expression 返回值表达式如果无返回值则可为 {@code null}
*/
public ReturnNode(ExpressionNode expression) {
this.expression = Optional.ofNullable(expression);
}
/**
* 获取可选的返回值表达式
*
* @return 如果有返回值则返回 {@code Optional.of(expression)}否则返回 {@code Optional.empty()}
*/
public Optional<ExpressionNode> getExpression() {
return expression;
}
}

View File

@ -0,0 +1,28 @@
package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/**
* {@code StringLiteralNode} 表示抽象语法树AST中的字符串字面量表达式节点
* <p>
* 用于表示源代码中出现的字符串常量 {@code "hello"}{@code "abc123"}
* 节点内部仅保存不带引号的字符串内容便于后续语义处理或编码
* </p>
*
* @param value 字符串常量的内容原始值中不包含双引号
*/
public record StringLiteralNode(String value) implements ExpressionNode {
/**
* 返回字符串字面量的带引号表示适用于语法树调试或文本输出
* <p>
* 例如 {@code value = Result:} 返回 {@code "Result:"}
* </p>
*
* @return 字符串字面量的完整表示形式带双引号
*/
@Override
public String toString() {
return "\"" + value + "\"";
}
}

View File

@ -0,0 +1,14 @@
package org.jcnc.snow.compiler.parser.ast.base;
/**
* {@code ExpressionNode} 表示抽象语法树AST中所有表达式类型节点的统一接口
* <p>
* 作为标记接口Marker Interface该接口用于区分表达式与其他语法结构
* 不定义具体方法其子类型通常表示可参与求值运算的结构
* 如常量表达式变量引用函数调用算术运算等
* </p>
* <p>
* 所有实现此接口的节点可参与表达式求值语义分析类型检查与中间代码生成等处理流程
* </p>
*/
public interface ExpressionNode extends Node {}

View File

@ -0,0 +1,18 @@
package org.jcnc.snow.compiler.parser.ast.base;
/**
* {@code Node} 是抽象语法树AST中所有语法节点的统一根接口
* <p>
* 作为标记接口Marker Interface该接口不定义任何方法
* 主要用于统一标识并组织 AST 体系中的各种语法构件节点包括
* </p>
* <ul>
* <li>{@link ExpressionNode}表达式节点如常量变量引用函数调用等</li>
* <li>{@link StatementNode}语句节点如声明赋值条件控制循环返回语句等</li>
* <li>模块函数参数等高层结构节点</li>
* </ul>
* <p>
* 所有 AST 处理逻辑如遍历分析代码生成均可基于该接口实现统一调度和类型判定
* </p>
*/
public interface Node {}

View File

@ -0,0 +1,11 @@
package org.jcnc.snow.compiler.parser.ast.base;
/**
* {@code StatementNode} 表示抽象语法树AST中所有语句结构的统一接口
* <p>
* 该接口为标记接口Marker Interface用于识别和区分语句类节点
* 包括但不限于变量声明赋值语句控制结构 ifloop返回语句等
* 实现此接口的类应表示程序在运行时执行的具体语法行为
* </p>
*/
public interface StatementNode extends Node {}

Some files were not shown because too many files have changed in this diff Show More