Compare commits
70 Commits
feature/fu
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b3d807bbc | |||
| 44b2ebb64a | |||
| eb7e2e7e93 | |||
| 77eddb83f4 | |||
| 4181ff8ae2 | |||
| 49b5e61e8e | |||
| e75a60026d | |||
| 83eb29fa3c | |||
| bc512fd02d | |||
| 0bbd82e9ce | |||
| 2fc2a93414 | |||
| 9bdc6245bf | |||
| 16ba11c391 | |||
| 19e473e93c | |||
| 5537a448e8 | |||
| caa62b0bfb | |||
| 3595631e2c | |||
| b033d53d28 | |||
| 8eda4fcbce | |||
| b02af0728a | |||
| b058ee4526 | |||
| 71cbb3a737 | |||
| ac0d3b9785 | |||
| f69dd85365 | |||
| c412d1194b | |||
| f39aece9cf | |||
| 439304c86a | |||
| 6e31185519 | |||
| 1c86c1dce7 | |||
| 1f8176d15e | |||
| f8a331809f | |||
| 0d6c6cef5a | |||
| 06bb647c83 | |||
| cc66eee75f | |||
| 1e2b0e4e0d | |||
| 1a0c3a38cb | |||
| fcdc3a49f8 | |||
| 139c42a90e | |||
| 2dfcc4c95e | |||
| 91aef32a42 | |||
| 18c9cbad7b | |||
| 310e909359 | |||
| d10b7336c1 | |||
| 3b39e9059b | |||
| c44f8a4630 | |||
| c4e9e541fb | |||
| da7d7bbcaa | |||
| d0c34ce1c2 | |||
| 1872221666 | |||
| 0a35289ad0 | |||
| 5f42beaf2a | |||
| b454d65962 | |||
| d536bd17ce | |||
| 3b3478ee30 | |||
| 926e87888c | |||
| f7db4fcce8 | |||
| 9afc1caac7 | |||
| 2a9d2b7f5f | |||
| f11303f0e8 | |||
| 7dd273759a | |||
| e626553a9c | |||
| b2c0b986c4 | |||
| a16f0ac059 | |||
| d3646c86ec | |||
| 39dff9efc5 | |||
| 80efd7c357 | |||
| 99345ddd19 | |||
| df07531edc | |||
| 5511978ed8 | |||
| 3406e1a2dd |
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto-generated by build\tools\generate-dotenv.ps1
|
||||
SNOW_VERSION=0.8.0
|
||||
@ -69,7 +69,7 @@ body:
|
||||
attributes:
|
||||
label: 软件版本/分支
|
||||
options:
|
||||
- v0.6.0
|
||||
- v0.8.0
|
||||
- main
|
||||
- dev
|
||||
- 其他
|
||||
|
||||
@ -7,4 +7,12 @@
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration default="false" name="Demo14" type="Application" factoryName="Application" folderName="Demo">
|
||||
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
|
||||
<module name="Snow" />
|
||||
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo14 -o target/Demo14" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="Demo22" type="Application" factoryName="Application" folderName="Demo">
|
||||
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
|
||||
<module name="Snow" />
|
||||
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo22 -o target/Demo22 --debug" />
|
||||
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo22 -o target/Demo22" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
10
.run/Demo23.run.xml
Normal file
10
.run/Demo23.run.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Demo23" type="Application" factoryName="Application" folderName="Demo">
|
||||
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
|
||||
<module name="Snow" />
|
||||
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo23 -o target/Demo23 --debug" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
10
.run/Demo24.run.xml
Normal file
10
.run/Demo24.run.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Demo24" type="Application" factoryName="Application" folderName="Demo">
|
||||
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
|
||||
<module name="Snow" />
|
||||
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo24 -o target/Demo24" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
10
.run/Demo25.run.xml
Normal file
10
.run/Demo25.run.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Demo25" type="Application" factoryName="Application" folderName="Demo">
|
||||
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
|
||||
<module name="Snow" />
|
||||
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo25 -o target/Demo25" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@ -1,7 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Run" type="CompoundRunConfigurationType">
|
||||
<toRun name="build_project2tar.ps1" type="PowerShellRunType" />
|
||||
<toRun name="Demo1" type="Application" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
6
.run/build-project2tar.ps1.run.xml
Normal file
6
.run/build-project2tar.ps1.run.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="build-project2tar.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build-project2tar.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@ -1,9 +1,9 @@
|
||||
<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">
|
||||
<configuration default="false" name="build-release-all.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build-release-all.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<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">
|
||||
<configuration default="false" name="build-release-all.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build-release-all.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
6
.run/release-linux.ps1.run.xml
Normal file
6
.run/release-linux.ps1.run.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="release-linux.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/release-linux.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
6
.run/release-windows.ps1.run.xml
Normal file
6
.run/release-windows.ps1.run.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="release-windows.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/release-windows.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@ -13,6 +13,11 @@
|
||||
<toRun name="Demo19" type="Application" />
|
||||
<toRun name="Demo2" type="Application" />
|
||||
<toRun name="Demo20" type="Application" />
|
||||
<toRun name="Demo21" type="Application" />
|
||||
<toRun name="Demo22" type="Application" />
|
||||
<toRun name="Demo23" type="Application" />
|
||||
<toRun name="Demo24" type="Application" />
|
||||
<toRun name="Demo25" type="Application" />
|
||||
<toRun name="Demo3" type="Application" />
|
||||
<toRun name="Demo4" type="Application" />
|
||||
<toRun name="Demo6" type="Application" />
|
||||
|
||||
57
Dockerfile
Normal file
57
Dockerfile
Normal file
@ -0,0 +1,57 @@
|
||||
# Stage 1: 官方 GraalVM 社区版(已含 native-image)
|
||||
FROM ghcr.io/graalvm/native-image-community:24.0.2 AS builder
|
||||
|
||||
RUN microdnf install -y \
|
||||
gcc gcc-c++ make git wget tar gzip which findutils maven \
|
||||
&& microdnf clean all
|
||||
|
||||
# ---------- 构建 musl ----------
|
||||
ARG MUSL_VER=1.2.5
|
||||
WORKDIR /tmp
|
||||
RUN wget -q https://musl.libc.org/releases/musl-${MUSL_VER}.tar.gz \
|
||||
&& tar -xzf musl-${MUSL_VER}.tar.gz \
|
||||
&& cd musl-${MUSL_VER} \
|
||||
&& ./configure --prefix=/opt/musl-${MUSL_VER} --disable-shared \
|
||||
&& make -j"$(nproc)" \
|
||||
&& make install \
|
||||
&& ln -s /opt/musl-${MUSL_VER} /opt/musl \
|
||||
&& cd / && rm -rf /tmp/musl-${MUSL_VER}*
|
||||
|
||||
RUN ln -s /opt/musl/bin/musl-gcc /usr/local/bin/x86_64-linux-musl-gcc \
|
||||
&& ln -s /opt/musl/bin/musl-gcc /usr/local/bin/x86_64-linux-musl-cc
|
||||
|
||||
ENV PATH="/opt/musl/bin:${PATH}"
|
||||
ENV CC="musl-gcc"
|
||||
ENV C_INCLUDE_PATH="/opt/musl/include"
|
||||
ENV LIBRARY_PATH="/opt/musl/lib"
|
||||
|
||||
# ---------- 静态 zlib ----------
|
||||
ARG ZLIB_VERSION=1.3.1
|
||||
WORKDIR /tmp
|
||||
RUN wget -q https://zlib.net/zlib-${ZLIB_VERSION}.tar.gz \
|
||||
&& tar -xzf zlib-${ZLIB_VERSION}.tar.gz \
|
||||
&& cd zlib-${ZLIB_VERSION} \
|
||||
&& CC=musl-gcc ./configure --static --prefix=/opt/musl \
|
||||
&& make -j"$(nproc)" \
|
||||
&& make install \
|
||||
&& cd / && rm -rf /tmp/zlib-${ZLIB_VERSION}*
|
||||
|
||||
# ---------- Maven 缓存优化 ----------
|
||||
WORKDIR /app
|
||||
COPY pom.xml ./
|
||||
|
||||
# 先拉依赖并缓存
|
||||
RUN mvn -B -P native-linux dependency:go-offline
|
||||
|
||||
# ---------- 复制源码 ----------
|
||||
COPY . /app
|
||||
|
||||
# ---------- 编译 native image ----------
|
||||
RUN mvn -P native-linux -DskipTests clean package
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Stage 2: 输出产物镜像(可以直接 cp 出二进制)
|
||||
# ------------------------------------------------------------
|
||||
FROM busybox AS export
|
||||
WORKDIR /export
|
||||
COPY --from=builder /app/org.jcnc.snow.cli.SnowCLI /export/Snow
|
||||
@ -11,8 +11,8 @@
|
||||
<a href="https://gitee.com/jcnc-org/snow/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/badge/%20license-Apache--2.0%20-blue" alt="">
|
||||
</a>
|
||||
<a href="https://gitee.com/jcnc-org/snow/tree/v0.6.0/">
|
||||
<img src="https://img.shields.io/badge/version-v0.6.0-blue" alt="">
|
||||
<a href="https://gitee.com/jcnc-org/snow/tree/v0.8.0/">
|
||||
<img src="https://img.shields.io/badge/version-v0.8.0-blue" alt="">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
48
build/build-project2tar.ps1
Normal file
48
build/build-project2tar.ps1
Normal file
@ -0,0 +1,48 @@
|
||||
# Set the tar package name
|
||||
$tarName = "Snow.tar"
|
||||
|
||||
# Get the script's current directory (build folder)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
# Get the parent directory (the project root)
|
||||
$parentDir = Split-Path -Parent $scriptDir
|
||||
|
||||
# Set the full path to the tar package
|
||||
$tarPath = Join-Path $parentDir $tarName
|
||||
|
||||
# Output message: starting to create tar package
|
||||
Write-Output "Starting to create tar package: $tarName in $parentDir ..."
|
||||
|
||||
# Remove old tar package if it exists
|
||||
if (Test-Path $tarPath) {
|
||||
Write-Output "Found an old $tarName, removing it..."
|
||||
Remove-Item $tarPath -Force
|
||||
}
|
||||
|
||||
# Make sure the tar command is available
|
||||
$tarCommand = "tar"
|
||||
if (-not (Get-Command $tarCommand -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "❌ 'tar' command is not available. Please make sure 'tar' is installed and can be run from PowerShell."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Execute tar: change to org\jcnc directory and compress the snow folder
|
||||
try {
|
||||
# Build the command and run it
|
||||
$tarCommandArgs = "-cf", $tarPath, "-C", "$scriptDir\..\src\main\java\org\jcnc", "snow"
|
||||
Write-Output "Running tar command: tar $tarCommandArgs"
|
||||
|
||||
& $tarCommand @tarCommandArgs
|
||||
} catch {
|
||||
Write-Error "❌ Failed to create tar package. Error: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if tar package was created successfully
|
||||
if (Test-Path $tarPath) {
|
||||
Write-Output "✅ Successfully created $tarName"
|
||||
exit 0
|
||||
} else {
|
||||
Write-Error "❌ Creation failed. Please check the tar command and paths."
|
||||
exit 1
|
||||
}
|
||||
129
build/build-release-all.ps1
Normal file
129
build/build-release-all.ps1
Normal file
@ -0,0 +1,129 @@
|
||||
param(
|
||||
[string]$LogDir = (Join-Path $PSScriptRoot 'target\parallel-logs')
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$winScript = Join-Path $PSScriptRoot 'release-windows.ps1'
|
||||
$linScript = Join-Path $PSScriptRoot 'release-linux.ps1'
|
||||
|
||||
if (-not (Test-Path $winScript)) { throw "File not found: $winScript" }
|
||||
if (-not (Test-Path $linScript)) { throw "File not found: $linScript" }
|
||||
|
||||
$winLogOut = [System.IO.Path]::GetTempFileName()
|
||||
$winLogErr = [System.IO.Path]::GetTempFileName()
|
||||
$linLogOut = [System.IO.Path]::GetTempFileName()
|
||||
$linLogErr = [System.IO.Path]::GetTempFileName()
|
||||
|
||||
$winProc = Start-Process powershell.exe -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File',"`"$winScript`"") `
|
||||
-RedirectStandardOutput $winLogOut -RedirectStandardError $winLogErr -NoNewWindow -PassThru
|
||||
$linProc = Start-Process powershell.exe -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File',"`"$linScript`"") `
|
||||
-RedirectStandardOutput $linLogOut -RedirectStandardError $linLogErr -NoNewWindow -PassThru
|
||||
|
||||
$winPosOut = 0
|
||||
$winPosErr = 0
|
||||
$linPosOut = 0
|
||||
$linPosErr = 0
|
||||
|
||||
Write-Host "===== Build Started ====="
|
||||
while (-not $winProc.HasExited -or -not $linProc.HasExited) {
|
||||
# windows-release stdout
|
||||
if (Test-Path $winLogOut) {
|
||||
$size = (Get-Item $winLogOut).Length
|
||||
if ($size -gt $winPosOut) {
|
||||
$fs = [System.IO.File]::Open($winLogOut, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
||||
$fs.Position = $winPosOut
|
||||
$sr = New-Object System.IO.StreamReader($fs)
|
||||
while (!$sr.EndOfStream) {
|
||||
$line = $sr.ReadLine()
|
||||
if ($line) { Write-Host "[windows-release][OUT] $line" }
|
||||
}
|
||||
$winPosOut = $fs.Position
|
||||
$sr.Close()
|
||||
$fs.Close()
|
||||
}
|
||||
}
|
||||
# windows-release stderr
|
||||
if (Test-Path $winLogErr) {
|
||||
$size = (Get-Item $winLogErr).Length
|
||||
if ($size -gt $winPosErr) {
|
||||
$fs = [System.IO.File]::Open($winLogErr, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
||||
$fs.Position = $winPosErr
|
||||
$sr = New-Object System.IO.StreamReader($fs)
|
||||
while (!$sr.EndOfStream) {
|
||||
$line = $sr.ReadLine()
|
||||
if ($line) { Write-Host "[windows-release][ERR] $line" -ForegroundColor Red }
|
||||
}
|
||||
$winPosErr = $fs.Position
|
||||
$sr.Close()
|
||||
$fs.Close()
|
||||
}
|
||||
}
|
||||
# linux-release stdout
|
||||
if (Test-Path $linLogOut) {
|
||||
$size = (Get-Item $linLogOut).Length
|
||||
if ($size -gt $linPosOut) {
|
||||
$fs = [System.IO.File]::Open($linLogOut, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
||||
$fs.Position = $linPosOut
|
||||
$sr = New-Object System.IO.StreamReader($fs)
|
||||
while (!$sr.EndOfStream) {
|
||||
$line = $sr.ReadLine()
|
||||
if ($line) { Write-Host "[linux-release][OUT] $line" }
|
||||
}
|
||||
$linPosOut = $fs.Position
|
||||
$sr.Close()
|
||||
$fs.Close()
|
||||
}
|
||||
}
|
||||
# linux-release stderr
|
||||
if (Test-Path $linLogErr) {
|
||||
$size = (Get-Item $linLogErr).Length
|
||||
if ($size -gt $linPosErr) {
|
||||
$fs = [System.IO.File]::Open($linLogErr, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
||||
$fs.Position = $linPosErr
|
||||
$sr = New-Object System.IO.StreamReader($fs)
|
||||
while (!$sr.EndOfStream) {
|
||||
$line = $sr.ReadLine()
|
||||
if ($line) { Write-Host "[linux-release][ERR] $line" -ForegroundColor Red }
|
||||
}
|
||||
$linPosErr = $fs.Position
|
||||
$sr.Close()
|
||||
$fs.Close()
|
||||
}
|
||||
}
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
|
||||
# After processes exit, print any remaining output
|
||||
$tasks = @(
|
||||
@{proc=$winProc; log=$winLogOut; tag='windows-release'; type='OUT'; skip=$winPosOut},
|
||||
@{proc=$winProc; log=$winLogErr; tag='windows-release'; type='ERR'; skip=$winPosErr},
|
||||
@{proc=$linProc; log=$linLogOut; tag='linux-release'; type='OUT'; skip=$linPosOut},
|
||||
@{proc=$linProc; log=$linLogErr; tag='linux-release'; type='ERR'; skip=$linPosErr}
|
||||
)
|
||||
foreach ($item in $tasks) {
|
||||
if (Test-Path $item.log) {
|
||||
$fs = [System.IO.File]::Open($item.log, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
||||
$fs.Position = $item.skip
|
||||
$sr = New-Object System.IO.StreamReader($fs)
|
||||
while (!$sr.EndOfStream) {
|
||||
$line = $sr.ReadLine()
|
||||
if ($line) {
|
||||
if ($item.type -eq 'ERR') {
|
||||
Write-Host "[$($item.tag)][ERR] $line" -ForegroundColor Red
|
||||
} else {
|
||||
Write-Host "[$($item.tag)][OUT] $line"
|
||||
}
|
||||
}
|
||||
}
|
||||
$sr.Close()
|
||||
$fs.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "All tasks completed successfully." -ForegroundColor Green
|
||||
|
||||
Remove-Item $winLogOut, $winLogErr, $linLogOut, $linLogErr -Force
|
||||
exit 0
|
||||
@ -1,47 +0,0 @@
|
||||
# 设定 tar 包的名称
|
||||
$tarName = "Snow.tar"
|
||||
|
||||
# 获取脚本当前目录(build文件夹)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
# 获取上一级目录(snow 根目录)
|
||||
$parentDir = Split-Path -Parent $scriptDir
|
||||
|
||||
# 设置 tar 包的完整路径
|
||||
$tarPath = Join-Path $parentDir $tarName
|
||||
|
||||
# 输出开始创建 tar 包的消息
|
||||
Write-Output "开始创建 tar 包: $tarName 到 $parentDir ..."
|
||||
|
||||
# 如果存在旧 tar 包,先删除它
|
||||
if (Test-Path $tarPath) {
|
||||
Write-Output "发现旧的 $tarName,正在删除..."
|
||||
Remove-Item $tarPath -Force
|
||||
}
|
||||
|
||||
# 确保 tar 命令可用
|
||||
$tarCommand = "tar"
|
||||
if (-not (Get-Command $tarCommand -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "❌ tar 命令不可用。请确保 tar 已安装并可在 PowerShell 中执行。"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 执行打包操作: 切换到 org\jcnc 目录下再压缩 snow 文件夹
|
||||
try {
|
||||
# 构建命令并执行
|
||||
$tarCommandArgs = "-cf", $tarPath, "-C", "$scriptDir\..\src\main\java\org\jcnc", "snow"
|
||||
Write-Output "执行 tar 命令: tar $tarCommandArgs"
|
||||
|
||||
& $tarCommand @tarCommandArgs
|
||||
} catch {
|
||||
Write-Error "❌ 创建 tar 包失败。错误信息: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 检查 tar 包是否创建成功
|
||||
if (Test-Path $tarPath) {
|
||||
Write-Output "✅ 成功创建 $tarName"
|
||||
} else {
|
||||
Write-Error "❌ 创建失败,请检查 tar 命令和路径是否正确。"
|
||||
exit 1
|
||||
}
|
||||
136
build/release-linux.ps1
Normal file
136
build/release-linux.ps1
Normal file
@ -0,0 +1,136 @@
|
||||
# run-linux-snow-export.ps1
|
||||
# Build and package linux-snow-export, version read from SNOW_VERSION in .env
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Import shared dotenv parser function
|
||||
. "$PSScriptRoot\tools\dotenv.ps1"
|
||||
|
||||
Write-Host "Step 0: Generate .env..."
|
||||
try {
|
||||
& "$PSScriptRoot\tools\generate-dotenv.ps1" -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Error "Failed to generate .env: $( $_.Exception.Message )"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Step 1: Build and run linux-snow-export..."
|
||||
docker compose run --build --rm linux-snow-export
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Build & Run failed, exiting script."
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Write-Host "Step 2: Run linux-snow-export without rebuild..."
|
||||
docker compose run --rm linux-snow-export
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Run without rebuild failed, exiting script."
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
# ===== Step 3: Read version from .env =====
|
||||
$projectRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$dotenvPath = Join-Path $projectRoot ".env"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $dotenvPath)) {
|
||||
Write-Error ".env not found at: $dotenvPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$version = Read-DotEnvValue -FilePath $dotenvPath -Key 'SNOW_VERSION'
|
||||
if (-not $version) {
|
||||
Write-Error "SNOW_VERSION not found in .env"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ===== Step 4: Define output paths =====
|
||||
$targetDir = Join-Path $projectRoot "target\release"
|
||||
$outDir = Join-Path $targetDir "Snow-v$version-linux-x64"
|
||||
$tgzPath = Join-Path $targetDir "Snow-v$version-linux-x64.tgz"
|
||||
|
||||
# ===== Step 5: Package to .tgz (no extra top-level dir, max compression) =====
|
||||
Write-Host "Step 5: Package to .tgz..."
|
||||
|
||||
if (-not (Test-Path -LiteralPath $outDir)) {
|
||||
Write-Error "Output directory not found: $outDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Ensure target directory exists
|
||||
if (-not (Test-Path -LiteralPath $targetDir)) {
|
||||
New-Item -ItemType Directory -Force -Path $targetDir | Out-Null
|
||||
}
|
||||
|
||||
# Remove old package if exists
|
||||
if (Test-Path -LiteralPath $tgzPath) {
|
||||
Write-Host "→ Removing existing tgz: $tgzPath"
|
||||
Remove-Item -LiteralPath $tgzPath -Force
|
||||
}
|
||||
|
||||
function Invoke-TarGz {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$SourceDir,
|
||||
[Parameter(Mandatory = $true)][string]$DestTgz
|
||||
)
|
||||
$tarExe = "tar"
|
||||
|
||||
$isWindows = $env:OS -eq 'Windows_NT'
|
||||
|
||||
if ($isWindows) {
|
||||
$psi = @{
|
||||
FilePath = $tarExe
|
||||
ArgumentList= @("-C", $SourceDir, "-czf", $DestTgz, ".")
|
||||
NoNewWindow = $true
|
||||
Wait = $true
|
||||
}
|
||||
try {
|
||||
$p = Start-Process @psi -PassThru -ErrorAction Stop
|
||||
$p.WaitForExit()
|
||||
if ($p.ExitCode -ne 0) {
|
||||
throw "tar exited with code $($p.ExitCode)"
|
||||
}
|
||||
} catch {
|
||||
throw "Packaging failed (Windows tar): $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$psi = @{
|
||||
FilePath = $tarExe
|
||||
ArgumentList= @("-C", $SourceDir, "-c", "-f", $DestTgz, "-I", "gzip -9", ".")
|
||||
NoNewWindow = $true
|
||||
Wait = $true
|
||||
}
|
||||
$p = Start-Process @psi -PassThru -ErrorAction Stop
|
||||
$p.WaitForExit()
|
||||
if ($p.ExitCode -eq 0) { return }
|
||||
} catch { }
|
||||
|
||||
try {
|
||||
$psi = @{
|
||||
FilePath = $tarExe
|
||||
ArgumentList= @("-C", $SourceDir, "-c", "-z", "-f", $DestTgz, ".")
|
||||
NoNewWindow = $true
|
||||
Wait = $true
|
||||
}
|
||||
$p = Start-Process @psi -PassThru -ErrorAction Stop
|
||||
$p.WaitForExit()
|
||||
if ($p.ExitCode -ne 0) {
|
||||
throw "tar exited with code $($p.ExitCode)"
|
||||
}
|
||||
} catch {
|
||||
throw "Packaging failed (Linux tar): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
Invoke-TarGz -SourceDir $outDir -DestTgz $tgzPath
|
||||
} catch {
|
||||
Write-Error $_.Exception.Message
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ">>> Package ready!" -ForegroundColor Green
|
||||
Write-Host "Version : $version"
|
||||
Write-Host "Output Dir : $outDir"
|
||||
Write-Host "Tgz File : $tgzPath"
|
||||
117
build/release-windows.ps1
Normal file
117
build/release-windows.ps1
Normal file
@ -0,0 +1,117 @@
|
||||
# release-windows.ps1
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
# Import shared dotenv parser function
|
||||
. "$PSScriptRoot\tools\dotenv.ps1"
|
||||
|
||||
# ===== Utility Functions =====
|
||||
function Find-PomUpwards([string]$startDir) {
|
||||
$dir = Resolve-Path $startDir
|
||||
while ($true) {
|
||||
$pom = Join-Path $dir "pom.xml"
|
||||
if (Test-Path $pom) { return $pom }
|
||||
$parent = Split-Path $dir -Parent
|
||||
if ($parent -eq $dir -or [string]::IsNullOrEmpty($parent)) { return $null }
|
||||
$dir = $parent
|
||||
}
|
||||
}
|
||||
|
||||
# ===== Step 0: Generate .env =====
|
||||
Write-Host "Step 0: Generate .env..."
|
||||
try {
|
||||
& "$PSScriptRoot\tools\generate-dotenv.ps1" -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Error "Failed to generate .env: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ===== Step 1: Locate project root & build =====
|
||||
Write-Host "Step 1: Locate project root and build..."
|
||||
$pom = Find-PomUpwards -startDir $PSScriptRoot
|
||||
if (-not $pom) {
|
||||
Write-Error "pom.xml not found. Please run this script within the project."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$projectRoot = Split-Path $pom -Parent
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
Write-Host "→ Running: mvn clean package"
|
||||
mvn clean package
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Maven build failed, exiting script."
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
# ===== Step 2: Read SNOW_VERSION =====
|
||||
Write-Host "Step 2: Read SNOW_VERSION from .env..."
|
||||
$dotenvPath = Join-Path $projectRoot ".env"
|
||||
$snowVersion = Read-DotEnvValue -FilePath $dotenvPath -Key "SNOW_VERSION"
|
||||
if (-not $snowVersion) {
|
||||
Write-Host "SNOW_VERSION not found in .env, using placeholder 0.0.0." -ForegroundColor Yellow
|
||||
$snowVersion = "0.0.0"
|
||||
}
|
||||
Write-Host "SNOW_VERSION = $snowVersion"
|
||||
|
||||
# ===== Step 3: Prepare release directory structure =====
|
||||
Write-Host "Step 3: Prepare release directory structure..."
|
||||
$targetDir = Join-Path $projectRoot "target"
|
||||
$exePath = Join-Path $targetDir "Snow.exe"
|
||||
if (-not (Test-Path $exePath)) {
|
||||
Write-Error "Expected build artifact not found: $exePath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$verName = "Snow-v${snowVersion}-windows-x64"
|
||||
$releaseRoot = Join-Path $targetDir "release"
|
||||
$outDir = Join-Path $releaseRoot $verName
|
||||
$binDir = Join-Path $outDir "bin"
|
||||
$libDir = Join-Path $outDir "lib"
|
||||
|
||||
# Clean old directory
|
||||
if (Test-Path $outDir) {
|
||||
Write-Host "→ Cleaning previous output directory..."
|
||||
Remove-Item $outDir -Recurse -Force
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
|
||||
Copy-Item -Path $exePath -Destination (Join-Path $binDir "Snow.exe") -Force
|
||||
Write-Host ">>> Collected Snow.exe"
|
||||
|
||||
# Optional lib
|
||||
$projectLib = Join-Path $projectRoot "lib"
|
||||
if (Test-Path $projectLib) {
|
||||
New-Item -ItemType Directory -Force -Path $libDir | Out-Null
|
||||
Copy-Item -Path (Join-Path $projectLib "*") -Destination $libDir -Recurse -Force
|
||||
Write-Host ">>> Copied lib directory"
|
||||
} else {
|
||||
Write-Host ">>> lib directory not found, skipping." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ===== Step 4: Create release zip =====
|
||||
Write-Host "Step 4: Create release zip..."
|
||||
New-Item -ItemType Directory -Force -Path $releaseRoot | Out-Null
|
||||
$zipPath = Join-Path $releaseRoot ("{0}.zip" -f $verName)
|
||||
if (Test-Path $zipPath) {
|
||||
Write-Host "→ Removing existing zip: $zipPath"
|
||||
Remove-Item $zipPath -Force
|
||||
}
|
||||
|
||||
try {
|
||||
Compress-Archive -Path (Join-Path $outDir '*') -DestinationPath $zipPath -CompressionLevel Optimal -Force
|
||||
} catch {
|
||||
Write-Error "Failed to create zip: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ">>> Package ready!" -ForegroundColor Green
|
||||
Write-Host "Version : $snowVersion"
|
||||
Write-Host "Output Dir : $outDir"
|
||||
Write-Host "Zip File : $zipPath"
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
53
build/tools/dotenv.ps1
Normal file
53
build/tools/dotenv.ps1
Normal file
@ -0,0 +1,53 @@
|
||||
# tools/dotenv.ps1
|
||||
# Unified .env reader function:
|
||||
# - Supports `KEY=VAL` and `export KEY=VAL`
|
||||
# - Skips blank lines and comments
|
||||
# - Handles quoted values (single or double quotes)
|
||||
# - Allows inline comments at the end of a line (space + #)
|
||||
# - If the same KEY is defined multiple times, the last one takes precedence
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
function Read-DotEnvValue {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$FilePath,
|
||||
[Parameter(Mandatory=$true)][string]$Key
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $FilePath)) { return $null }
|
||||
|
||||
# Match the target key (escaped), allowing optional "export" prefix
|
||||
$pattern = '^(?:\s*export\s+)?(?<k>' + [regex]::Escape($Key) + ')\s*=\s*(?<v>.*)$'
|
||||
$value = $null
|
||||
|
||||
# Read line by line for large file compatibility
|
||||
Get-Content -LiteralPath $FilePath | ForEach-Object {
|
||||
$line = $_
|
||||
|
||||
# Skip blank lines and full-line comments
|
||||
if ($line -match '^\s*$') { return }
|
||||
if ($line -match '^\s*#') { return }
|
||||
|
||||
if ($line -match $pattern) {
|
||||
$v = $matches['v']
|
||||
|
||||
# Remove surrounding quotes if present
|
||||
$trimmed = $v.Trim()
|
||||
if ($trimmed -match '^\s*"(.*)"\s*$') {
|
||||
$v = $matches[1]
|
||||
} elseif ($trimmed -match "^\s*'(.*)'\s*$") {
|
||||
$v = $matches[1]
|
||||
} else {
|
||||
# Strip inline comments (space + # …), ignoring escaped \#
|
||||
if ($v -match '^(.*?)(?<!\\)\s+#.*$') {
|
||||
$v = $matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
$value = $v.Trim()
|
||||
}
|
||||
}
|
||||
|
||||
return $value
|
||||
}
|
||||
23
build/tools/generate-dotenv.ps1
Normal file
23
build/tools/generate-dotenv.ps1
Normal file
@ -0,0 +1,23 @@
|
||||
# build\tools\generate-dotenv.ps1
|
||||
|
||||
# Repository root: go up two levels from build\tools\
|
||||
$repoRoot = (Get-Item $PSScriptRoot).Parent.Parent.FullName
|
||||
$envPath = Join-Path $repoRoot ".env"
|
||||
$pomPath = Join-Path $repoRoot "pom.xml"
|
||||
|
||||
if (-not (Test-Path $pomPath -PathType Leaf)) {
|
||||
throw "pom.xml not found: $pomPath"
|
||||
}
|
||||
|
||||
[xml]$pom = Get-Content $pomPath -Encoding UTF8
|
||||
$version = $pom.project.version
|
||||
|
||||
$lines = @(
|
||||
"# Auto-generated by build\tools\generate-dotenv.ps1"
|
||||
"SNOW_VERSION=$version"
|
||||
)
|
||||
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllLines($envPath, $lines, $utf8NoBom)
|
||||
Write-Host "Generated/overwritten $envPath (version: $version)"
|
||||
return
|
||||
24
docker-compose.yml
Normal file
24
docker-compose.yml
Normal file
@ -0,0 +1,24 @@
|
||||
services:
|
||||
# Run with: docker compose run --rm linux-snow-export
|
||||
linux-snow-export:
|
||||
build:
|
||||
context: .
|
||||
target: export
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
ver="Snow-v${SNOW_VERSION}-linux-x64"
|
||||
mkdir -p "/output/release/$$ver/bin"
|
||||
cp /export/Snow "/output/release/$$ver/bin/"
|
||||
if [ -d /export/lib ]; then
|
||||
mkdir -p "/output/release/$$ver/lib"
|
||||
cp -a /export/lib/. "/output/release/$$ver/lib/"
|
||||
fi
|
||||
tar -C /output/release -czf "/output/release/$$ver.tgz" "$$ver"
|
||||
volumes:
|
||||
- ./target:/output
|
||||
- ./lib:/export/lib:ro
|
||||
env_file:
|
||||
- .env
|
||||
@ -1,46 +1,15 @@
|
||||
module: Main
|
||||
import: os
|
||||
import: ModuleA,os
|
||||
function: main
|
||||
params:
|
||||
returns: int
|
||||
body:
|
||||
declare x: int = 0
|
||||
|
||||
x = inc(x)
|
||||
println(x)
|
||||
|
||||
x = inc(x, 7)
|
||||
println(x)
|
||||
|
||||
return 0
|
||||
end body
|
||||
end function
|
||||
|
||||
function: inc
|
||||
params:
|
||||
declare num: int
|
||||
returns: int
|
||||
body:
|
||||
return num + 1
|
||||
end body
|
||||
end function
|
||||
|
||||
function: inc
|
||||
params:
|
||||
declare num: int
|
||||
declare v: int
|
||||
returns: int
|
||||
body:
|
||||
return num + v
|
||||
end body
|
||||
end function
|
||||
|
||||
function: println
|
||||
params:
|
||||
declare i1: int
|
||||
returns: void
|
||||
body:
|
||||
os.syscall("PRINTLN",i1)
|
||||
declare sum: int = ModuleA.a+2
|
||||
os.print(sum+1)
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
|
||||
module: ModuleA
|
||||
globals:
|
||||
declare const a: int =10
|
||||
end module
|
||||
|
||||
11
playground/Demo/Demo22/OS.snow
Normal file
11
playground/Demo/Demo22/OS.snow
Normal file
@ -0,0 +1,11 @@
|
||||
module: os
|
||||
import: os
|
||||
function: print
|
||||
params:
|
||||
declare i1: int
|
||||
returns: void
|
||||
body:
|
||||
syscall("PRINT",i1)
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
14
playground/Demo/Demo23/Main.snow
Normal file
14
playground/Demo/Demo23/Main.snow
Normal file
@ -0,0 +1,14 @@
|
||||
module: Main
|
||||
import: ModuleA
|
||||
function: main
|
||||
returns: void
|
||||
body:
|
||||
declare sum: byte = ModuleA.a
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
|
||||
module: ModuleA
|
||||
globals:
|
||||
declare const a: byte = 2b
|
||||
end module
|
||||
26
playground/Demo/Demo24/Main.snow
Normal file
26
playground/Demo/Demo24/Main.snow
Normal file
@ -0,0 +1,26 @@
|
||||
module: Main
|
||||
import: ModuleA,os
|
||||
globals:
|
||||
declare const c: int = 10
|
||||
function: main
|
||||
returns: void
|
||||
body:
|
||||
declare a: int = ModuleA.sum(c,2)
|
||||
os.print(a)
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
|
||||
module: ModuleA
|
||||
globals:
|
||||
declare const a: int = 10
|
||||
function: sum
|
||||
params:
|
||||
declare a: int
|
||||
declare b: int
|
||||
returns: int
|
||||
body:
|
||||
return a + b
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
11
playground/Demo/Demo24/OS.snow
Normal file
11
playground/Demo/Demo24/OS.snow
Normal file
@ -0,0 +1,11 @@
|
||||
module: os
|
||||
import: os
|
||||
function: print
|
||||
params:
|
||||
declare i1: int
|
||||
returns: void
|
||||
body:
|
||||
syscall("PRINT",i1)
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
23
playground/Demo/Demo25/Main.snow
Normal file
23
playground/Demo/Demo25/Main.snow
Normal file
@ -0,0 +1,23 @@
|
||||
module: Main
|
||||
import: os,ModuleA
|
||||
globals:
|
||||
declare c: int = 10
|
||||
function: main
|
||||
returns: void
|
||||
body:
|
||||
c = ModuleA.getA()
|
||||
os.print(c)
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
|
||||
module: ModuleA
|
||||
globals:
|
||||
declare a: int = 2
|
||||
function: getA
|
||||
returns: int
|
||||
body:
|
||||
return a
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
11
playground/Demo/Demo25/OS.snow
Normal file
11
playground/Demo/Demo25/OS.snow
Normal file
@ -0,0 +1,11 @@
|
||||
module: os
|
||||
import: os
|
||||
function: print
|
||||
params:
|
||||
declare i1: int
|
||||
returns: void
|
||||
body:
|
||||
syscall("PRINT",i1)
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
20
playground/Demo/Demo26/Main.snow
Normal file
20
playground/Demo/Demo26/Main.snow
Normal file
@ -0,0 +1,20 @@
|
||||
module: Main
|
||||
function: main
|
||||
returns: void
|
||||
body:
|
||||
// 1. 常见转义符
|
||||
declare sNewline : string = "换行示例:\n第二行"
|
||||
declare sTab : string = "制表符示例:\t列二"
|
||||
declare sBackslash: string = "反斜杠示例: C:\\Snow"
|
||||
declare sDQuote : string = "双引号示例: \"Snow\""
|
||||
declare sSQuote : string = "单引号示例: \'Snow\'"
|
||||
declare sCarriage : string = "回车示例:\rCarriage"
|
||||
declare sBackspace: string = "退格示例: ABC\bD"
|
||||
declare sFormFeed : string = "换页示例:\fPage-2"
|
||||
|
||||
// 2. Unicode 转义
|
||||
declare sUnicode : string = "𪚥𠮷: \u4F60\u597D, Snow!"
|
||||
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
48
pom.xml
48
pom.xml
@ -7,7 +7,7 @@
|
||||
|
||||
<groupId>org.jcnc.snow</groupId>
|
||||
<artifactId>Snow</artifactId>
|
||||
<version>0.6.0</version>
|
||||
<version>0.8.0</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@ -70,11 +70,7 @@
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<!--
|
||||
原生镜像构建: Linux 平台
|
||||
- 使用 GraalVM 的 native-image 工具,生成静态链接的可执行文件
|
||||
- 依赖 musl libc,需提前安装并配置 musl-gcc 工具链
|
||||
-->
|
||||
<!-- 原生镜像构建: Linux 平台(使用 Docker builder 中的 musl 工具链) -->
|
||||
<profile>
|
||||
<id>native-linux</id>
|
||||
<activation>
|
||||
@ -82,25 +78,39 @@
|
||||
<family>unix</family>
|
||||
</os>
|
||||
</activation>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.graalvm.buildtools</groupId>
|
||||
<artifactId>native-maven-plugin</artifactId>
|
||||
<version>${native.maven.plugin.version}</version>
|
||||
<!-- 启用插件扩展,允许在 build 生命周期中无须额外配置 -->
|
||||
<extensions>true</extensions>
|
||||
|
||||
<configuration>
|
||||
<mainClass>org.jcnc.snow.cli.SnowCLI</mainClass>
|
||||
<imageName>Snow</imageName>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<buildArgs>
|
||||
<buildArg>--static</buildArg>
|
||||
<buildArg>--libc=musl</buildArg>
|
||||
<buildArg>--emit=build-report</buildArg>
|
||||
<buildArg>-O2</buildArg>
|
||||
<buildArg>-H:Class=org.jcnc.snow.cli.SnowCLI</buildArg>
|
||||
<buildArg>-H:CCompilerPath=/opt/musl/bin/musl-gcc</buildArg>
|
||||
<buildArg>-H:CLibraryPath=/opt/musl/lib</buildArg>
|
||||
</buildArgs>
|
||||
</configuration>
|
||||
|
||||
<executions>
|
||||
<!-- 打包阶段生成原生可执行文件 -->
|
||||
<execution>
|
||||
<id>build-native</id>
|
||||
<goals>
|
||||
<!-- compile-no-fork 在当前 JVM 进程中执行 native-image -->
|
||||
<goal>compile-no-fork</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
</execution>
|
||||
<!-- 测试阶段运行原生镜像的测试 -->
|
||||
|
||||
<execution>
|
||||
<id>test-native</id>
|
||||
<goals>
|
||||
@ -109,24 +119,6 @@
|
||||
<phase>test</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<buildArgs>
|
||||
<!-- 静态链接 -->
|
||||
<buildArg>--static</buildArg>
|
||||
<!-- 指定 musl libc -->
|
||||
<buildArg>--libc=musl</buildArg>
|
||||
<!-- 输出构建报告 -->
|
||||
<buildArg>--emit build-report</buildArg>
|
||||
<!-- 优化级别 O2 -->
|
||||
<buildArg>-O2</buildArg>
|
||||
</buildArgs>
|
||||
<environment>
|
||||
<!-- 指定使用 musl 工具链 -->
|
||||
<PATH>/opt/musl-1.2.5/bin:${env.PATH}</PATH>
|
||||
<C_INCLUDE_PATH>/opt/musl-1.2.5/include</C_INCLUDE_PATH>
|
||||
<LIBRARY_PATH>/opt/musl-1.2.5/lib</LIBRARY_PATH>
|
||||
</environment>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
82
src/main/java/org/jcnc/snow/common/StringEscape.java
Normal file
82
src/main/java/org/jcnc/snow/common/StringEscape.java
Normal file
@ -0,0 +1,82 @@
|
||||
package org.jcnc.snow.common;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 字符串转义/反转义工具类,主要用于:
|
||||
* <ul>
|
||||
* <li><b>编译期</b>:将运行时的字符串安全地编码为单行形式(用于 .water 指令文件的保存)。</li>
|
||||
* <li><b>运行期</b>:在虚拟机(VM)执行相关指令时,将转义后的字符串还原成真实字符。</li>
|
||||
* </ul>
|
||||
* <br>
|
||||
* 转义规则兼容 Java 字符串转义(包括 \n, \t, \r 等常见控制字符),同时对于不可见或非 ASCII 字符,会编码为 Unicode 形式(如 <code>uXXXX</code>)。
|
||||
* </p>
|
||||
*/
|
||||
public final class StringEscape {
|
||||
|
||||
/**
|
||||
* 工具类私有构造方法,禁止实例化。
|
||||
*/
|
||||
private StringEscape() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>运行期方法:</b>
|
||||
* <p>将转义序列还原为实际字符。</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>支持常见的转义字符序列。</li>
|
||||
* <li>支持 uXXXX 形式的 Unicode 字符反转义。</li>
|
||||
* <li>对于无法识别的转义,按原样输出。</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param src 含有转义序列的字符串
|
||||
* @return 反转义后的字符串,原样还原
|
||||
*/
|
||||
public static String unescape(String src) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
for (int i = 0; i < src.length(); i++) {
|
||||
char c = src.charAt(i);
|
||||
if (c != '\\') { // 非转义字符,直接输出
|
||||
out.append(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果是最后一个字符为反斜杠,则原样输出
|
||||
if (i == src.length() - 1) {
|
||||
out.append('\\');
|
||||
break;
|
||||
}
|
||||
|
||||
char n = src.charAt(++i); // 下一个字符
|
||||
switch (n) {
|
||||
case 'n' -> out.append('\n'); // 换行
|
||||
case 't' -> out.append('\t'); // 制表符
|
||||
case 'r' -> out.append('\r'); // 回车
|
||||
case 'b' -> out.append('\b'); // 退格
|
||||
case 'f' -> out.append('\f'); // 换页
|
||||
case '\\' -> out.append('\\'); // 反斜杠
|
||||
case '"' -> out.append('"'); // 双引号
|
||||
case '\'' -> out.append('\''); // 单引号
|
||||
case 'u' -> {
|
||||
// Unicode 转义,需读取接下来的 4 位十六进制数字
|
||||
if (i + 4 <= src.length() - 1) {
|
||||
String hex = src.substring(i + 1, i + 5);
|
||||
try {
|
||||
out.append((char) Integer.parseInt(hex, 16));
|
||||
i += 4;
|
||||
} catch (NumberFormatException ignore) {
|
||||
// 非法 hex,原样输出
|
||||
out.append("\\u").append(hex);
|
||||
i += 4;
|
||||
}
|
||||
} else {
|
||||
// 字符串末尾长度不足,原样输出
|
||||
out.append("\\u");
|
||||
}
|
||||
}
|
||||
default -> out.append(n); // 其他未定义的转义序列,原样输出
|
||||
}
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
@ -72,8 +72,7 @@ public class CallGenerator implements InstructionGenerator<CallInstruction> {
|
||||
String fn = ins.getFunctionName();
|
||||
|
||||
// 特殊处理 syscall 调用
|
||||
String tname = fn.substring(0, fn.lastIndexOf(':'));
|
||||
if ("syscall".equals(tname) || tname.endsWith(".syscall")) {
|
||||
if ("syscall".equals(fn) || fn.endsWith(".syscall")) {
|
||||
generateSyscall(ins, out, slotMap, fn);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -12,46 +12,47 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <b>LoadConstGenerator - Generates VM instructions from IR {@code LoadConstInstruction}</b>
|
||||
* <b>LoadConstGenerator</b>
|
||||
*
|
||||
* <p>
|
||||
* This class is responsible for converting IR-level {@link LoadConstInstruction} into corresponding VM instructions.
|
||||
* This generator converts an IR-level {@link LoadConstInstruction} into corresponding VM instructions.
|
||||
* If the constant is a {@code String}, it will also be registered in the
|
||||
* {@link CallGenerator} string constant pool to support syscall downgrade scenarios.
|
||||
* {@link CallGenerator} string constant pool for later use.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Fix: When the constant is an array (List), type information is preserved in R_PUSH payload:
|
||||
* Key implementation notes:
|
||||
* <ul>
|
||||
* <li>Float is output with <code>f</code> suffix (e.g., 0.1f);</li>
|
||||
* <li>Long is output with <code>L</code> suffix (e.g., 123L);</li>
|
||||
* <li>Double/Integer are output in their default format (e.g., 1.0, 42);</li>
|
||||
* <li>Supports recursive serialization of nested arrays.</li>
|
||||
* <li>When the constant is an array (List), type information is preserved in the R_PUSH payload:</li>
|
||||
* <li>Float values get an <code>f</code> suffix (e.g., 0.1f)</li>
|
||||
* <li>Long values get an <code>L</code> suffix (e.g., 123L)</li>
|
||||
* <li>Double and Integer values use their default string format (e.g., 1.0, 42)</li>
|
||||
* <li>Nested arrays are recursively serialized with correct type suffixes.</li>
|
||||
* </ul>
|
||||
* This prevents float values from being misinterpreted as double on the VM side,
|
||||
* and avoids Double→Float cast exceptions in later F_STORE operations.
|
||||
* This prevents type confusion on the VM side (e.g., float being misread as double)
|
||||
* and avoids cast exceptions during store operations.
|
||||
* </p>
|
||||
*/
|
||||
public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruction> {
|
||||
|
||||
/**
|
||||
* Formats a constant value as a string for use as a VM payload.
|
||||
* Lists are recursively serialized, and Float/Long types include suffixes to preserve type information.
|
||||
* Formats a constant value for use as a VM instruction payload.
|
||||
* For lists, recursively formats each element with type suffixes where appropriate.
|
||||
*
|
||||
* @param v The constant value to format.
|
||||
* @return The formatted string for use in VM code.
|
||||
* @param v The constant value.
|
||||
* @return The formatted string payload for VM code.
|
||||
*/
|
||||
private static String formatConst(Object v) {
|
||||
return formatConst(v, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper for recursively formatting constant values (including nested arrays)
|
||||
* with appropriate type suffixes for array payloads.
|
||||
* Recursively formats constant values (including nested arrays), preserving
|
||||
* type suffixes and escaping strings. Used internally for array/list handling.
|
||||
*
|
||||
* @param v The constant value to format.
|
||||
* @param insideArray True if currently formatting inside an array context; affects whether type suffixes are applied.
|
||||
* @return The formatted string for use in VM code.
|
||||
* @param v The constant value.
|
||||
* @param insideArray Whether this value is inside an array context (controls type suffixing).
|
||||
* @return The formatted string for VM code.
|
||||
*/
|
||||
private static String formatConst(Object v, boolean insideArray) {
|
||||
if (v instanceof List<?> list) {
|
||||
@ -61,10 +62,10 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
||||
.collect(Collectors.joining(", ")) + "]";
|
||||
}
|
||||
if (v instanceof String s) {
|
||||
return s;
|
||||
// Escape and wrap the string in double quotes, to avoid line breaks or control chars breaking VM code
|
||||
return "\"" + escape(s) + "\"";
|
||||
}
|
||||
if (v instanceof Float f) {
|
||||
// Always keep .0 for integer values
|
||||
float fv = f;
|
||||
String s = (fv == (long) fv) ? String.format("%.1f", fv) : f.toString();
|
||||
return insideArray ? (s + "f") : s;
|
||||
@ -74,7 +75,6 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
||||
}
|
||||
if (v instanceof Double d) {
|
||||
double dv = d;
|
||||
// Always keep .0 for integer values
|
||||
return (dv == (long) dv) ? String.format("%.1f", dv) : Double.toString(dv);
|
||||
}
|
||||
if (v instanceof Short s) {
|
||||
@ -89,28 +89,54 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
||||
return String.valueOf(v);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the type of IR instruction supported by this generator.
|
||||
* Escapes a string for use in VM code: replaces control characters and all non-ASCII characters
|
||||
* with their corresponding escape sequences, so the .water file remains single-line and parseable.
|
||||
* Supported escapes: \n, \r, \t, \f, \b, \", \', \\, and Unicode escapes like "uXXXX" for non-ASCII.
|
||||
*
|
||||
* @return The class object representing {@link LoadConstInstruction}.
|
||||
* @param s The input string.
|
||||
* @return The escaped string.
|
||||
*/
|
||||
private static String escape(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
char ch = s.charAt(i);
|
||||
switch (ch) {
|
||||
case '\n' -> sb.append("\\n");
|
||||
case '\r' -> sb.append("\\r");
|
||||
case '\t' -> sb.append("\\t");
|
||||
case '\f' -> sb.append("\\f");
|
||||
case '\b' -> sb.append("\\b");
|
||||
case '\"' -> sb.append("\\\"");
|
||||
case '\'' -> sb.append("\\'");
|
||||
case '\\' -> sb.append("\\\\");
|
||||
default -> {
|
||||
// Escape non-ASCII and control characters using uXXXX
|
||||
if (ch < 0x20 || ch > 0x7E) {
|
||||
sb.append(String.format("\\u%04X", (int) ch));
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<LoadConstInstruction> supportedClass() {
|
||||
return LoadConstInstruction.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the VM instructions for a given {@link LoadConstInstruction}.
|
||||
* <p>
|
||||
* This includes formatting the constant value, emitting the corresponding PUSH and STORE instructions,
|
||||
* marking the local slot type for later operations, and registering string constants if necessary.
|
||||
* </p>
|
||||
* Generates VM code for a LoadConstInstruction.
|
||||
* Produces PUSH and STORE instructions, sets the slot type,
|
||||
* and registers string constants if necessary.
|
||||
*
|
||||
* @param ins The {@link LoadConstInstruction} to generate code for.
|
||||
* @param out The {@link VMProgramBuilder} used to collect the generated instructions.
|
||||
* @param slotMap A mapping from {@link IRVirtualRegister} to physical slot indices.
|
||||
* @param currentFn The name of the current function.
|
||||
* @param ins The IR instruction to generate.
|
||||
* @param out The output program builder.
|
||||
* @param slotMap The mapping from IR virtual register to physical slot.
|
||||
* @param currentFn The current function name.
|
||||
*/
|
||||
@Override
|
||||
public void generate(LoadConstInstruction ins,
|
||||
@ -118,19 +144,19 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
||||
Map<IRVirtualRegister, Integer> slotMap,
|
||||
String currentFn) {
|
||||
|
||||
// 1. Get the constant value
|
||||
// 1. Retrieve the constant value from the instruction
|
||||
IRConstant constant = (IRConstant) ins.operands().getFirst();
|
||||
Object value = constant.value();
|
||||
|
||||
// 2. Generate PUSH instruction (array constants use type-aware formatting)
|
||||
// 2. Format and emit the PUSH instruction (arrays will use type-aware formatting)
|
||||
String payload = formatConst(value);
|
||||
out.emit(OpHelper.pushOpcodeFor(value) + " " + payload);
|
||||
|
||||
// 3. STORE the result to the destination slot
|
||||
// 3. Emit STORE to the destination slot
|
||||
int slot = slotMap.get(ins.dest());
|
||||
out.emit(OpHelper.storeOpcodeFor(value) + " " + slot);
|
||||
|
||||
// 4. Mark the slot's data type for later inference and instruction selection
|
||||
// 4. Mark the slot's data type for later use (type inference, instruction selection, etc.)
|
||||
char prefix = switch (value) {
|
||||
case Integer _ -> 'I'; // Integer
|
||||
case Long _ -> 'L'; // Long
|
||||
@ -138,15 +164,15 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
||||
case Byte _ -> 'B'; // Byte
|
||||
case Double _ -> 'D'; // Double
|
||||
case Float _ -> 'F'; // Float
|
||||
case Boolean _ -> 'I'; // Boolean handled as Integer (typically lowered to 1/0)
|
||||
case String _ -> 'R'; // String constant
|
||||
case java.util.List<?> _ -> 'R'; // Reference type (arrays, etc.)
|
||||
case Boolean _ -> 'I'; // Booleans are treated as integers (1/0)
|
||||
case String _ -> 'R'; // Reference type for strings
|
||||
case java.util.List<?> _ -> 'R'; // Reference type for arrays/lists
|
||||
case null, default -> throw new IllegalStateException("Unknown constant type: "
|
||||
+ (value != null ? value.getClass() : null));
|
||||
};
|
||||
out.setSlotType(slot, prefix);
|
||||
|
||||
// 5. If the constant is a string, register it for the CallGenerator string pool
|
||||
// 5. Register the string constant for the string constant pool if needed
|
||||
if (value instanceof String s) {
|
||||
CallGenerator.registerStringConst(ins.dest().id(), s);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jcnc.snow.compiler.ir.builder;
|
||||
|
||||
import org.jcnc.snow.compiler.ir.common.GlobalConstTable;
|
||||
import org.jcnc.snow.compiler.ir.core.IROpCode;
|
||||
import org.jcnc.snow.compiler.ir.core.IRValue;
|
||||
import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
|
||||
@ -14,9 +15,6 @@ import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static org.jcnc.snow.compiler.ir.utils.ExpressionUtils.looksLikeFloat;
|
||||
|
||||
/**
|
||||
* {@code ExpressionBuilder} 表达式 → IR 构建器。
|
||||
@ -51,6 +49,8 @@ public record ExpressionBuilder(IRContext ctx) {
|
||||
if (reg == null) throw new IllegalStateException("未定义标识符: " + id.name());
|
||||
yield reg;
|
||||
}
|
||||
// 模块常量 / 全局变量,如 ModuleA.a
|
||||
case MemberExpressionNode mem -> buildMember(mem);
|
||||
// 二元表达式(如 a+b, x==y)
|
||||
case BinaryExpressionNode bin -> buildBinary(bin);
|
||||
// 函数/方法调用表达式
|
||||
@ -65,6 +65,40 @@ public record ExpressionBuilder(IRContext ctx) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 成员访问表达式构建
|
||||
*
|
||||
* @param mem 成员表达式节点
|
||||
* @return 存储结果的虚拟寄存器
|
||||
*/
|
||||
private IRVirtualRegister buildMember(MemberExpressionNode mem) {
|
||||
if (!(mem.object() instanceof IdentifierNode id)) {
|
||||
throw new IllegalStateException("不支持的成员访问对象类型: "
|
||||
+ mem.object().getClass().getSimpleName());
|
||||
}
|
||||
String qualified = id.name() + "." + mem.member();
|
||||
|
||||
/* 1) 尝试直接获取已有寄存器绑定 */
|
||||
IRVirtualRegister reg = ctx.getScope().lookup(qualified);
|
||||
if (reg != null) {
|
||||
return reg;
|
||||
}
|
||||
|
||||
/* 2) 折叠为编译期常量:先查作用域,再查全局常量表 */
|
||||
Object v = ctx.getScope().getConstValue(qualified);
|
||||
if (v == null) {
|
||||
v = GlobalConstTable.get(qualified);
|
||||
}
|
||||
if (v != null) {
|
||||
IRVirtualRegister r = ctx.newRegister();
|
||||
ctx.addInstruction(new LoadConstInstruction(r, new IRConstant(v)));
|
||||
return r;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("未定义的常量: " + qualified);
|
||||
}
|
||||
|
||||
|
||||
/* ───────────────── 写入指定寄存器 ───────────────── */
|
||||
|
||||
/**
|
||||
@ -380,73 +414,24 @@ public record ExpressionBuilder(IRContext ctx) {
|
||||
// 1. 递归生成所有参数的寄存器
|
||||
List<IRVirtualRegister> argv = call.arguments().stream().map(this::build).toList();
|
||||
|
||||
// 2. 生成参数列表描述,即(类型_类型)这种格式
|
||||
StringJoiner sj = new StringJoiner("_");
|
||||
for (ExpressionNode param : call.arguments()) {
|
||||
switch (param) {
|
||||
case NumberLiteralNode n -> {
|
||||
String value = n.value();
|
||||
char suffix = value.isEmpty() ? '\0'
|
||||
: Character.toLowerCase(value.charAt(value.length() - 1));
|
||||
|
||||
switch (suffix) {
|
||||
case 'b' -> sj.add("byte");
|
||||
case 's' -> sj.add("short");
|
||||
case 'l' -> sj.add("long");
|
||||
case 'f' -> sj.add("float");
|
||||
case 'd' -> sj.add("double");
|
||||
default -> {
|
||||
if (looksLikeFloat(value)) {
|
||||
sj.add("double");
|
||||
} else {
|
||||
sj.add("int");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case BoolLiteralNode _ -> sj.add("bool");
|
||||
case StringLiteralNode _ -> sj.add("string");
|
||||
case IdentifierNode id -> {
|
||||
String type = ctx.getScope().lookupType(id.name());
|
||||
sj.add(type);
|
||||
}
|
||||
// case CallExpressionNode ce -> {
|
||||
// }
|
||||
case null, default -> throw new IllegalArgumentException("(内部错误) 不支持的参数表达式: " + param);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 规范化被调用方法名(区分成员方法与普通函数)
|
||||
// 2. 规范化被调用方法名(区分成员方法与普通函数)
|
||||
String callee = switch (call.callee()) {
|
||||
// 成员方法调用,如 obj.method()
|
||||
case MemberExpressionNode m when m.object() instanceof IdentifierNode id -> {
|
||||
String qualifiedName = id.name() + "." + m.member();
|
||||
if (sj.length() > 0) {
|
||||
qualifiedName = qualifiedName + ":" + sj;
|
||||
}
|
||||
|
||||
yield qualifiedName;
|
||||
}
|
||||
case MemberExpressionNode m when m.object() instanceof IdentifierNode id -> id.name() + "." + m.member();
|
||||
// 普通函数调用,或处理命名空间前缀(如当前方法名为 namespace.func)
|
||||
case IdentifierNode id -> {
|
||||
String current = ctx.getFunction().name();
|
||||
int dot = current.lastIndexOf('.');
|
||||
|
||||
String qualifiedName = dot > 0
|
||||
? current.substring(0, dot) + "." + id.name()
|
||||
: id.name();
|
||||
if (sj.length() > 0) {
|
||||
qualifiedName = qualifiedName + ":" + sj;
|
||||
}
|
||||
|
||||
yield qualifiedName; // 全局函数调用
|
||||
if (dot > 0)
|
||||
yield current.substring(0, dot) + "." + id.name(); // 同命名空间内调用
|
||||
yield id.name(); // 全局函数调用
|
||||
}
|
||||
// 其它类型不支持
|
||||
default -> throw new IllegalStateException(
|
||||
"不支持的调用目标: " + call.callee().getClass().getSimpleName());
|
||||
};
|
||||
|
||||
// 4. 分配用于存放返回值的新寄存器,并生成 Call 指令
|
||||
// 3. 分配用于存放返回值的新寄存器,并生成 Call 指令
|
||||
IRVirtualRegister dest = ctx.newRegister();
|
||||
ctx.addInstruction(new CallInstruction(dest, callee, new ArrayList<>(argv)));
|
||||
return dest;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jcnc.snow.compiler.ir.builder;
|
||||
|
||||
import org.jcnc.snow.compiler.ir.common.GlobalConstTable;
|
||||
import org.jcnc.snow.compiler.ir.common.GlobalFunctionTable;
|
||||
import org.jcnc.snow.compiler.ir.core.IRFunction;
|
||||
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
|
||||
@ -13,6 +14,13 @@ import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
|
||||
* <p>
|
||||
* 负责将语法树中的 FunctionNode 节点转化为可执行的 IRFunction,
|
||||
* 包含参数声明、返回类型推断、函数体语句转换等步骤。
|
||||
*
|
||||
* <ul>
|
||||
* <li>支持自动导入全局/跨模块常量,使跨模块常量引用(如 ModuleA.a)在 IR 阶段可用。</li>
|
||||
* <li>将函数形参声明为虚拟寄存器,并注册到作用域,便于后续指令生成。</li>
|
||||
* <li>根据返回类型设置表达式默认字面量类型,保证 IR 层类型一致性。</li>
|
||||
* <li>遍历并转换函数体语句为 IR 指令。</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class FunctionBuilder {
|
||||
|
||||
@ -21,11 +29,15 @@ public class FunctionBuilder {
|
||||
* <p>
|
||||
* 构建过程包括:
|
||||
* <ol>
|
||||
* <li>初始化 IRFunction 实例和上下文</li>
|
||||
* <li>根据函数返回类型,设置默认类型后缀,便于表达式推断</li>
|
||||
* <li>声明参数到作用域,并为每个参数分配虚拟寄存器</li>
|
||||
* <li>遍历并转换函数体内的每条语句为 IR 指令</li>
|
||||
* <li>函数构建完成后,清理默认类型后缀,防止影响其他函数</li>
|
||||
* <li>在全局函数表注册函数签名,便于后续模块/语义分析阶段查询。</li>
|
||||
* <li>初始化 IRFunction 实例和 IRContext 上下文对象(包含作用域与寄存器信息)。</li>
|
||||
* <li>自动导入全局常量(包括跨模块 const 变量)到当前作用域,
|
||||
* 使成员访问如 ModuleA.a 可直接折叠为常量。</li>
|
||||
* <li>根据函数返回类型,设置表达式推断的默认字面量类型后缀
|
||||
* (如 double→d, float→f),避免类型不一致。</li>
|
||||
* <li>遍历声明形参,每个参数分配虚拟寄存器,并注册到作用域。</li>
|
||||
* <li>依次转换函数体中的每条语句为 IR 指令。</li>
|
||||
* <li>函数体转换完成后,清理默认类型后缀,防止影响后续函数构建。</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param functionNode 表示函数定义的语法树节点
|
||||
@ -33,44 +45,56 @@ public class FunctionBuilder {
|
||||
*/
|
||||
public IRFunction build(FunctionNode functionNode) {
|
||||
|
||||
// 在全局函数表中注册函数信息
|
||||
// 1. 在全局函数表注册函数名与返回类型
|
||||
// 方便其他阶段/模块调用、类型检查。
|
||||
GlobalFunctionTable.register(functionNode.name(), functionNode.returnType());
|
||||
|
||||
|
||||
// 0) 基本初始化: 创建 IRFunction 实例与对应上下文
|
||||
// 2. 初始化 IRFunction 实例与上下文对象
|
||||
// IRFunction: 表示该函数的中间代码容器
|
||||
// IRContext: 负责作用域、寄存器分配等编译上下文管理
|
||||
IRFunction irFunction = new IRFunction(functionNode.name());
|
||||
IRContext irContext = new IRContext(irFunction);
|
||||
|
||||
// 1) 把函数返回类型注入为默认类型后缀(供表达式类型推断)
|
||||
// 3. 自动导入所有全局/跨模块常量到当前作用域
|
||||
// 支持如 ModuleA.a 这样的常量访问/折叠(参见 ExpressionBuilder)
|
||||
GlobalConstTable.all().forEach((k, v) ->
|
||||
irContext.getScope().importExternalConst(k, v));
|
||||
|
||||
// 4. 根据函数返回类型设置默认类型后缀
|
||||
// 例如返回类型为 double 时, 字面量表达式自动用 d 后缀。
|
||||
char _returnSuffix = switch (functionNode.returnType().toLowerCase()) {
|
||||
case "double" -> 'd';
|
||||
case "float" -> 'f';
|
||||
case "long" -> 'l';
|
||||
case "short" -> 's';
|
||||
case "byte" -> 'b';
|
||||
default -> '\0';
|
||||
default -> '\0'; // 其它类型不设默认后缀
|
||||
};
|
||||
ExpressionUtils.setDefaultSuffix(_returnSuffix);
|
||||
|
||||
try {
|
||||
// 2) 声明形参: 为每个参数分配虚拟寄存器并声明到作用域
|
||||
// 5. 遍历函数参数列表
|
||||
// - 为每个参数分配一个新的虚拟寄存器
|
||||
// - 注册参数名、类型、寄存器到当前作用域
|
||||
// - 添加参数寄存器到 IRFunction(用于后续调用与指令生成)
|
||||
for (ParameterNode p : functionNode.parameters()) {
|
||||
IRVirtualRegister reg = irFunction.newRegister(); // 新寄存器
|
||||
irContext.getScope().declare(p.name(), p.type(), reg); // 变量名→寄存器绑定
|
||||
irFunction.addParameter(reg, p.type()); // 添加到函数参数列表
|
||||
IRVirtualRegister reg = irFunction.newRegister();
|
||||
irContext.getScope().declare(p.name(), p.type(), reg);
|
||||
irFunction.addParameter(reg);
|
||||
}
|
||||
|
||||
// 3) 生成函数体 IR: 遍历每条语句,逐一转化
|
||||
// 6. 遍历函数体语句节点,转换为 IR 指令
|
||||
// StatementBuilder 负责将每条语句递归转换为 IR
|
||||
StatementBuilder stmtBuilder = new StatementBuilder(irContext);
|
||||
for (StatementNode stmt : functionNode.body()) {
|
||||
stmtBuilder.build(stmt);
|
||||
}
|
||||
} finally {
|
||||
// 4) 清除默认后缀,避免影响后续函数的推断
|
||||
// 7. 清理默认类型后缀,防止影响后续其他函数的类型推断
|
||||
ExpressionUtils.clearDefaultSuffix();
|
||||
}
|
||||
|
||||
// 返回构建好的 IRFunction
|
||||
// 8. 返回构建完成的 IRFunction
|
||||
return irFunction;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,30 +15,25 @@ import java.util.Map;
|
||||
* <li>支持变量与虚拟寄存器的重新绑定与查找</li>
|
||||
* <li>支持变量的类型信息记录与查询</li>
|
||||
* <li>支持变量的编译期常量值记录与查询(便于常量折叠等优化)</li>
|
||||
* <li>支持跨模块全局常量(如 ModuleA.a)查找</li>
|
||||
* </ul>
|
||||
*/
|
||||
final class IRBuilderScope {
|
||||
|
||||
/**
|
||||
* 变量名到虚拟寄存器的映射表。
|
||||
* 用于跟踪所有声明和分配的变量。
|
||||
*/
|
||||
/** 变量名到虚拟寄存器的映射表(本地变量) */
|
||||
private final Map<String, IRVirtualRegister> vars = new HashMap<>();
|
||||
/** 变量名到类型字符串的映射表 */
|
||||
private final Map<String, String> varTypes = new HashMap<>();
|
||||
/** 变量名到编译期常量值的映射表(本作用域) */
|
||||
private final Map<String, Object> varConstValues = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 变量名到类型字符串的映射表。
|
||||
* 用于类型分析与推断。
|
||||
*/
|
||||
private final Map<String, String> varTypes = new HashMap<>();
|
||||
/**
|
||||
* 变量名到编译期常量值的映射表。
|
||||
* 用于常量折叠优化(如 int、string、数组等常量)。
|
||||
*/
|
||||
private final Map<String, Object> varConstValues = new HashMap<>();
|
||||
/**
|
||||
* 当前作用域所绑定的 IRFunction 实例。
|
||||
* 用于申请新的虚拟寄存器。
|
||||
* 额外:存放跨模块导入的全局常量
|
||||
* key 形如 "ModuleA.a" value 为其常量值
|
||||
*/
|
||||
private final Map<String, Object> externalConsts = new HashMap<>();
|
||||
|
||||
/** 当前作用域所绑定的 IRFunction 实例 */
|
||||
private IRFunction fn;
|
||||
|
||||
/**
|
||||
@ -118,7 +113,7 @@ final class IRBuilderScope {
|
||||
// ---------------- 编译期常量相关接口 ----------------
|
||||
|
||||
/**
|
||||
* 设置变量的编译期常量值。
|
||||
* 设置变量的编译期常量值(本地变量)。
|
||||
*
|
||||
* @param name 变量名称
|
||||
* @param value 常量值(null 表示清除)
|
||||
@ -129,21 +124,38 @@ final class IRBuilderScope {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取变量的编译期常量值(如没有则返回 null)。
|
||||
* 获取变量的编译期常量值(本地变量或导入的外部常量)。
|
||||
* <br>
|
||||
* 优先查找本地常量,未命中再查外部(如 "ModuleA.a")。
|
||||
*
|
||||
* @param name 变量名称
|
||||
* @param name 变量名称或"模块名.常量名"
|
||||
* @return 编译期常量值,或 null
|
||||
*/
|
||||
Object getConstValue(String name) {
|
||||
return varConstValues.get(name);
|
||||
Object v = varConstValues.get(name);
|
||||
if (v != null) return v;
|
||||
// 支持跨模块常量/全局变量
|
||||
return externalConsts.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除变量的编译期常量值绑定。
|
||||
* 清除变量的编译期常量值绑定(本地)。
|
||||
*
|
||||
* @param name 变量名称
|
||||
*/
|
||||
void clearConstValue(String name) {
|
||||
varConstValues.remove(name);
|
||||
}
|
||||
|
||||
// ---------------- 跨模块常量导入支持 ----------------
|
||||
|
||||
/**
|
||||
* 导入外部(其他模块)的全局常量/变量。
|
||||
*
|
||||
* @param qualifiedName 形如 "ModuleA.a"
|
||||
* @param value 其常量值
|
||||
*/
|
||||
void importExternalConst(String qualifiedName, Object value) {
|
||||
externalConsts.put(qualifiedName, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,26 +1,26 @@
|
||||
package org.jcnc.snow.compiler.ir.builder;
|
||||
|
||||
import org.jcnc.snow.compiler.ir.common.GlobalConstTable;
|
||||
import org.jcnc.snow.compiler.ir.core.IRFunction;
|
||||
import org.jcnc.snow.compiler.ir.core.IRProgram;
|
||||
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.ModuleNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.ParameterNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.*;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.Node;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.NodeContext;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* IRProgramBuilder 负责将 AST 根节点(如模块、函数、顶层语句)转换为可执行的 IRProgram 实例。
|
||||
* <p>
|
||||
* 主要职责:
|
||||
* IRProgramBuilder 负责将 AST 顶层节点转换为可执行的 {@link IRProgram}。
|
||||
*
|
||||
* <ul>
|
||||
* <li>遍历 AST 根节点,根据类型分别处理(模块、函数、顶层语句)。</li>
|
||||
* <li>对模块内的函数添加全限定名,并在函数体前注入全局变量声明。</li>
|
||||
* <li>将单独的顶层语句封装为特殊的 "_start" 函数。</li>
|
||||
* <li>预扫描所有模块,将 <code>declare const</code> 常量登记到全局常量表,支持跨模块常量折叠。</li>
|
||||
* <li>对模块内的函数加上模块前缀,保证命名唯一,并将本模块全局声明注入到函数体前部。</li>
|
||||
* <li>将独立顶层语句自动包装为特殊的 "_start" 函数(脚本模式支持)。</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class IRProgramBuilder {
|
||||
@ -33,20 +33,23 @@ public final class IRProgramBuilder {
|
||||
* @throws IllegalStateException 遇到不支持的顶层节点类型时抛出
|
||||
*/
|
||||
public IRProgram buildProgram(List<Node> roots) {
|
||||
// 预先收集并登记全部模块常量到全局常量表
|
||||
preloadGlobals(roots);
|
||||
|
||||
IRProgram irProgram = new IRProgram();
|
||||
for (Node node : roots) {
|
||||
switch (node) {
|
||||
case ModuleNode moduleNode -> {
|
||||
// 处理模块节点:遍历其中所有函数,统一用“模块名.函数名”作为全限定名
|
||||
// 处理模块节点:遍历其中所有函数,统一用“模块名.函数名”作为全限定名,避免命名冲突
|
||||
for (FunctionNode f : moduleNode.functions()) {
|
||||
irProgram.add(buildFunctionWithGlobals(moduleNode, f));
|
||||
}
|
||||
}
|
||||
case FunctionNode functionNode ->
|
||||
// 处理顶层函数节点:直接构建为 IRFunction 并加入
|
||||
// 处理顶层函数节点:直接构建为 IRFunction 并加入
|
||||
irProgram.add(buildFunction(functionNode));
|
||||
case StatementNode statementNode ->
|
||||
// 处理脚本式顶层语句:封装成 "_start" 函数后构建并添加
|
||||
// 处理脚本式顶层语句:封装成 "_start" 函数后构建并添加
|
||||
irProgram.add(buildFunction(wrapTopLevel(statementNode)));
|
||||
default ->
|
||||
// 遇到未知类型节点,抛出异常
|
||||
@ -56,34 +59,111 @@ public final class IRProgramBuilder {
|
||||
return irProgram;
|
||||
}
|
||||
|
||||
// ===================== 全局常量收集 =====================
|
||||
|
||||
/**
|
||||
* 扫描所有模块节点,将其中声明的 const 全局变量(compile-time 常量)
|
||||
* 以 "模块名.常量名" 形式注册到全局常量表。
|
||||
* 支持跨模块常量折叠,用于后续 IR 生成过程中的常量折叠优化。
|
||||
*
|
||||
* @param roots AST 顶层节点列表
|
||||
*/
|
||||
private void preloadGlobals(List<Node> roots) {
|
||||
for (Node n : roots) {
|
||||
if (n instanceof ModuleNode mod) {
|
||||
String moduleName = mod.name();
|
||||
if (mod.globals() == null) continue;
|
||||
for (DeclarationNode decl : mod.globals()) {
|
||||
// 只处理 compile-time 的 const 常量,并要求有初始值
|
||||
if (!decl.isConst() || decl.getInitializer().isEmpty()) continue;
|
||||
ExpressionNode init = decl.getInitializer().get();
|
||||
Object value = evalLiteral(init);
|
||||
if (value != null) {
|
||||
GlobalConstTable.register(moduleName + "." + decl.getName(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字面量提取与类型折叠工具。
|
||||
* 用于将表达式节点还原为 Java 原生类型(int、long、double、String等),仅支持直接字面量。
|
||||
*
|
||||
* @param expr 要计算的表达式节点
|
||||
* @return 提取到的原生常量值,不支持的情况返回 null
|
||||
*/
|
||||
private Object evalLiteral(ExpressionNode expr) {
|
||||
return switch (expr) {
|
||||
case NumberLiteralNode num -> {
|
||||
String raw = num.value();
|
||||
String s = raw.replace("_", "");
|
||||
char last = Character.toLowerCase(s.charAt(s.length() - 1));
|
||||
String core = switch (last) {
|
||||
case 'b', 's', 'l', 'f', 'd' -> s.substring(0, s.length() - 1);
|
||||
default -> s;
|
||||
};
|
||||
try {
|
||||
if (core.contains(".") || core.contains("e") || core.contains("E")) {
|
||||
// 浮点数处理
|
||||
yield Double.parseDouble(core);
|
||||
}
|
||||
long lv = Long.parseLong(core);
|
||||
yield switch (last) {
|
||||
case 'b' -> (byte) lv;
|
||||
case 's' -> (short) lv;
|
||||
case 'l' -> lv;
|
||||
default -> (int) lv;
|
||||
};
|
||||
} catch (NumberFormatException ignore) {
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
case StringLiteralNode str -> str.value();
|
||||
case BoolLiteralNode b -> b.getValue() ? 1 : 0;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
// ===================== IRFunction 构建辅助 =====================
|
||||
|
||||
/**
|
||||
* 构建带有模块全局声明“注入”的函数,并将函数名加上模块前缀,保证模块内函数名唯一。
|
||||
* <p>
|
||||
* 如果模块有全局声明,则这些声明会被插入到函数体前部。
|
||||
* 如果模块有全局声明,则这些声明会被插入到函数体前部(**会过滤掉与参数同名的全局声明**)。
|
||||
*
|
||||
* @param moduleNode 当前模块节点
|
||||
* @param functionNode 模块中的函数节点
|
||||
* @return 包含全局声明、已加前缀函数名的 IRFunction
|
||||
* @param moduleNode 所属模块节点
|
||||
* @param functionNode 待构建的函数节点
|
||||
* @return 包含全局声明的 IRFunction
|
||||
*/
|
||||
private IRFunction buildFunctionWithGlobals(ModuleNode moduleNode, FunctionNode functionNode) {
|
||||
// 拼接模块名和函数名,生成全限定名
|
||||
String qualifiedName = moduleNode.name() + "." + functionNode.name();
|
||||
|
||||
StringJoiner sj = new StringJoiner("_");
|
||||
for (ParameterNode param : functionNode.parameters()) {
|
||||
sj.add(param.type());
|
||||
}
|
||||
if (sj.length() > 0) {
|
||||
qualifiedName = qualifiedName + ":" + sj;
|
||||
}
|
||||
|
||||
// 若无全局声明,仅重命名后直接构建
|
||||
if (moduleNode.globals() == null || moduleNode.globals().isEmpty()) {
|
||||
// 无全局声明,直接重命名构建
|
||||
return buildFunction(renameFunction(functionNode, qualifiedName));
|
||||
}
|
||||
// 若有全局声明,插入到函数体最前面
|
||||
List<StatementNode> newBody = new ArrayList<>(moduleNode.globals().size() + functionNode.body().size());
|
||||
newBody.addAll(moduleNode.globals());
|
||||
|
||||
// ------- 过滤与参数重名的全局声明 -------
|
||||
Set<String> paramNames = new HashSet<>();
|
||||
for (ParameterNode p : functionNode.parameters()) {
|
||||
paramNames.add(p.name());
|
||||
}
|
||||
List<StatementNode> filteredGlobals = new ArrayList<>();
|
||||
for (DeclarationNode g : moduleNode.globals()) {
|
||||
// 避免全局声明和参数重名,优先参数
|
||||
if (!paramNames.contains(g.getName())) {
|
||||
filteredGlobals.add(g);
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredGlobals.isEmpty()) {
|
||||
// 过滤后已无可插入的全局声明
|
||||
return buildFunction(renameFunction(functionNode, qualifiedName));
|
||||
}
|
||||
|
||||
// 合并全局声明与函数体,前插全局声明
|
||||
List<StatementNode> newBody = new ArrayList<>(filteredGlobals.size() + functionNode.body().size());
|
||||
newBody.addAll(filteredGlobals);
|
||||
newBody.addAll(functionNode.body());
|
||||
FunctionNode wrapped = new FunctionNode(
|
||||
qualifiedName,
|
||||
@ -96,11 +176,11 @@ public final class IRProgramBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个重命名的 FunctionNode(只修改函数名,其他属性保持不变)。
|
||||
* 生成一个重命名的 FunctionNode(只修改函数名,其他属性保持不变)。
|
||||
*
|
||||
* @param fn 原始函数节点
|
||||
* @param newName 新的函数名(通常为全限定名)
|
||||
* @return 重命名后的 FunctionNode
|
||||
* @param newName 新的函数名(全限定名)
|
||||
* @return 重命名后的函数节点
|
||||
*/
|
||||
private FunctionNode renameFunction(FunctionNode fn, String newName) {
|
||||
return new FunctionNode(
|
||||
@ -115,8 +195,8 @@ public final class IRProgramBuilder {
|
||||
/**
|
||||
* 构建 IRFunction。
|
||||
*
|
||||
* @param functionNode 要转换的函数节点
|
||||
* @return 转换结果 IRFunction
|
||||
* @param functionNode 待构建的 FunctionNode
|
||||
* @return 构建后的 IRFunction
|
||||
*/
|
||||
private IRFunction buildFunction(FunctionNode functionNode) {
|
||||
return new FunctionBuilder().build(functionNode);
|
||||
@ -124,11 +204,10 @@ public final class IRProgramBuilder {
|
||||
|
||||
/**
|
||||
* 将顶层语句节点封装成特殊的 "_start" 函数。
|
||||
* <p>
|
||||
* 这对于脚本式文件支持至关重要(即文件最外层直接写语句)。
|
||||
* 主要用于脚本模式支持,使得顶层语句也可以被 IR 执行引擎统一处理。
|
||||
*
|
||||
* @param stmt 要封装的顶层语句
|
||||
* @return 包装成 FunctionNode 的 "_start" 函数
|
||||
* @param stmt 顶层语句节点
|
||||
* @return 封装后的 FunctionNode
|
||||
*/
|
||||
private FunctionNode wrapTopLevel(StatementNode stmt) {
|
||||
return new FunctionNode(
|
||||
|
||||
@ -6,23 +6,16 @@ import org.jcnc.snow.compiler.ir.value.IRConstant;
|
||||
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
|
||||
|
||||
/**
|
||||
* InstructionFactory —— 统一生成并注册 IR 指令的工厂类。
|
||||
* <p>
|
||||
* 该类封装了常见的 IR 指令生成方式,包括常量加载、二元运算、赋值、控制流等,
|
||||
* 统一简化指令插入和寄存器分配逻辑,提升 IR 生成阶段的代码可维护性和复用性。
|
||||
* </p>
|
||||
* IR 指令统一生成工厂类,负责封装常量加载、二元运算、赋值、控制流等指令生成逻辑。
|
||||
* 提高 IR 生成阶段的可维护性与复用性。
|
||||
*/
|
||||
public class InstructionFactory {
|
||||
|
||||
/* ====================================================================== */
|
||||
/* 常量 / 通用二元运算(新寄存器) */
|
||||
/* ====================================================================== */
|
||||
|
||||
/**
|
||||
* 加载整数常量,将其写入一个新分配的虚拟寄存器,并返回该寄存器。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文(用于分配寄存器与添加指令)
|
||||
* @param value 要加载的整数常量值
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param value 整数常量值
|
||||
* @return 存储该常量的新虚拟寄存器
|
||||
*/
|
||||
public static IRVirtualRegister loadConst(IRContext ctx, int value) {
|
||||
@ -31,88 +24,150 @@ public class InstructionFactory {
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 long 类型常量到新寄存器。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param value long 类型常量值
|
||||
* @return 存储该常量的新虚拟寄存器
|
||||
*/
|
||||
public static IRVirtualRegister loadConst(IRContext ctx, long value) {
|
||||
IRVirtualRegister r = ctx.newRegister();
|
||||
ctx.addInstruction(new LoadConstInstruction(r, new IRConstant(value)));
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 float 类型常量到新寄存器。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param value float 类型常量值
|
||||
* @return 存储该常量的新虚拟寄存器
|
||||
*/
|
||||
public static IRVirtualRegister loadConst(IRContext ctx, float value) {
|
||||
IRVirtualRegister r = ctx.newRegister();
|
||||
ctx.addInstruction(new LoadConstInstruction(r, new IRConstant(value)));
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 double 类型常量到新寄存器。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param value double 类型常量值
|
||||
* @return 存储该常量的新虚拟寄存器
|
||||
*/
|
||||
public static IRVirtualRegister loadConst(IRContext ctx, double value) {
|
||||
IRVirtualRegister r = ctx.newRegister();
|
||||
ctx.addInstruction(new LoadConstInstruction(r, new IRConstant(value)));
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行二元运算(如加法、减法等),结果写入新分配的虚拟寄存器并返回该寄存器。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param op 运算类型(IROpCode 枚举,如 ADD_I32 等)
|
||||
* @param a 第一个操作数寄存器
|
||||
* @param b 第二个操作数寄存器
|
||||
* @return 保存运算结果的新虚拟寄存器
|
||||
* @param op 二元运算操作码
|
||||
* @param a 左操作数寄存器
|
||||
* @param b 右操作数寄存器
|
||||
* @return 存储结果的新虚拟寄存器
|
||||
*/
|
||||
public static IRVirtualRegister binOp(IRContext ctx, IROpCode op,
|
||||
IRVirtualRegister a, IRVirtualRegister b) {
|
||||
public static IRVirtualRegister binOp(IRContext ctx, IROpCode op, IRVirtualRegister a, IRVirtualRegister b) {
|
||||
IRVirtualRegister dest = ctx.newRegister();
|
||||
ctx.addInstruction(new BinaryOperationInstruction(op, dest, a, b));
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
/* 直接写入指定寄存器 */
|
||||
/* ====================================================================== */
|
||||
|
||||
/**
|
||||
* 加载整数常量到指定虚拟寄存器。
|
||||
* 加载常量到指定寄存器。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param dest 目标寄存器
|
||||
* @param value 要加载的整数常量
|
||||
* @param dest 目标虚拟寄存器
|
||||
* @param value IR 常量值
|
||||
*/
|
||||
public static void loadConstInto(IRContext ctx, IRVirtualRegister dest, IRConstant value) {
|
||||
ctx.addInstruction(new LoadConstInstruction(dest, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对两个寄存器执行二元运算,将结果写入指定目标寄存器。
|
||||
* 执行二元运算,并将结果写入指定寄存器。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param op 运算类型(IROpCode 枚举)
|
||||
* @param a 第一个操作数寄存器
|
||||
* @param b 第二个操作数寄存器
|
||||
* @param dest 运算结果目标寄存器
|
||||
* @param op 二元运算操作码
|
||||
* @param a 左操作数寄存器
|
||||
* @param b 右操作数寄存器
|
||||
* @param dest 目标虚拟寄存器
|
||||
*/
|
||||
public static void binOpInto(IRContext ctx, IROpCode op,
|
||||
IRVirtualRegister a, IRVirtualRegister b,
|
||||
IRVirtualRegister dest) {
|
||||
public static void binOpInto(IRContext ctx, IROpCode op, IRVirtualRegister a, IRVirtualRegister b, IRVirtualRegister dest) {
|
||||
ctx.addInstruction(new BinaryOperationInstruction(op, dest, a, b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Move 指令(src → dest)。若寄存器相同也安全。
|
||||
* <p>
|
||||
* 实现方式: dest = src + 0(即加上常量 0)。
|
||||
* </p>
|
||||
* 生成“值拷贝”语义(src → dest)。
|
||||
* 若类型无法推断,默认采用 int 方案(ADD_I32, src+0)。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param src 源寄存器
|
||||
* @param dest 目标寄存器
|
||||
*/
|
||||
public static void move(IRContext ctx, IRVirtualRegister src, IRVirtualRegister dest) {
|
||||
// 自赋值无需任何操作,避免生成多余的常量 0 寄存器
|
||||
if (src == dest) {
|
||||
return;
|
||||
}
|
||||
// 回退实现: dest = src + 0
|
||||
IRVirtualRegister zero = loadConst(ctx, 0);
|
||||
ctx.addInstruction(new BinaryOperationInstruction(IROpCode.ADD_I32, dest, src, zero));
|
||||
String varType = ctx.getVarType(); // 需要 IRContext 提供
|
||||
char suffix = '\0';
|
||||
if (varType != null) {
|
||||
switch (varType) {
|
||||
case "byte" -> suffix = 'b';
|
||||
case "short" -> suffix = 's';
|
||||
case "int" -> suffix = 'i';
|
||||
case "long" -> suffix = 'l';
|
||||
case "float" -> suffix = 'f';
|
||||
case "double" -> suffix = 'd';
|
||||
}
|
||||
}
|
||||
IRVirtualRegister zero;
|
||||
IROpCode op = switch (suffix) {
|
||||
case 'd' -> {
|
||||
zero = loadConst(ctx, 0.0);
|
||||
yield IROpCode.ADD_D64;
|
||||
}
|
||||
case 'f' -> {
|
||||
zero = loadConst(ctx, 0.0f);
|
||||
yield IROpCode.ADD_F32;
|
||||
}
|
||||
case 'l' -> {
|
||||
zero = loadConst(ctx, 0L);
|
||||
yield IROpCode.ADD_L64;
|
||||
}
|
||||
case 's' -> {
|
||||
zero = loadConst(ctx, 0);
|
||||
yield IROpCode.ADD_S16;
|
||||
}
|
||||
case 'b' -> {
|
||||
zero = loadConst(ctx, 0);
|
||||
yield IROpCode.ADD_B8;
|
||||
}
|
||||
default -> {
|
||||
zero = loadConst(ctx, 0);
|
||||
yield IROpCode.ADD_I32;
|
||||
}
|
||||
};
|
||||
ctx.addInstruction(new BinaryOperationInstruction(op, dest, src, zero));
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
/* 控制流指令 */
|
||||
/* ====================================================================== */
|
||||
|
||||
/**
|
||||
* 生成无条件跳转(JMP)指令,跳转到指定标签。
|
||||
* 生成无条件跳转指令。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param label 目标标签名
|
||||
* @param label 跳转目标标签
|
||||
*/
|
||||
public static void jmp(IRContext ctx, String label) {
|
||||
ctx.addInstruction(new IRJumpInstruction(label));
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 IR 中插入一个标签(Label)。
|
||||
* 在 IR 流中插入标签。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param label 标签名
|
||||
@ -122,22 +177,18 @@ public class InstructionFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较跳转(如 if a < b goto label),根据条件跳转到目标标签。
|
||||
* 比较两个寄存器,并根据结果跳转到指定标签。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
* @param cmp 比较操作码(如 IROpCode.LT_I32 等)
|
||||
* @param a 第一个操作数寄存器
|
||||
* @param b 第二个操作数寄存器
|
||||
* @param cmp 比较操作码
|
||||
* @param a 左操作数寄存器
|
||||
* @param b 右操作数寄存器
|
||||
* @param targetLabel 跳转目标标签
|
||||
*/
|
||||
public static void cmpJump(IRContext ctx, IROpCode cmp,
|
||||
IRVirtualRegister a, IRVirtualRegister b,
|
||||
String targetLabel) {
|
||||
public static void cmpJump(IRContext ctx, IROpCode cmp, IRVirtualRegister a, IRVirtualRegister b, String targetLabel) {
|
||||
ctx.addInstruction(new IRCompareJumpInstruction(cmp, a, b, targetLabel));
|
||||
}
|
||||
|
||||
/* ---------------- 返回 ---------------- */
|
||||
|
||||
/**
|
||||
* 生成返回指令(带返回值)。
|
||||
*
|
||||
@ -149,7 +200,7 @@ public class InstructionFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成无返回值的 return 指令(如 void 函数)。
|
||||
* 生成无返回值(void)返回指令。
|
||||
*
|
||||
* @param ctx 当前 IR 上下文
|
||||
*/
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
package org.jcnc.snow.compiler.ir.common;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 全局常量表,用于跨模块编译期常量查询和折叠。
|
||||
*
|
||||
* <p>
|
||||
* 主要功能:
|
||||
* <ul>
|
||||
* <li>在 IRProgramBuilder 预扫描阶段,将所有模块级 <code>const</code> 常量
|
||||
* (如 ModuleA.a)注册到全局常量表,支持跨模块访问。</li>
|
||||
* <li>后续任何阶段均可通过 {@link #get(String)} 查询已注册常量,实现编译期常量折叠。</li>
|
||||
* <li>保证线程安全,支持并发注册和访问。</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 常量的 key 格式为“模块名.常量名”,如 "ModuleA.a",以便唯一标识。
|
||||
*
|
||||
* <p>
|
||||
* 典型用法:
|
||||
* <pre>
|
||||
* GlobalConstTable.register("ModuleA.a", 10); // 注册常量
|
||||
* Object val = GlobalConstTable.get("ModuleA.a"); // 查询常量
|
||||
* </pre>
|
||||
*/
|
||||
public final class GlobalConstTable {
|
||||
|
||||
/** 存储全局常量: “ModuleName.constName” → 常量值。线程安全。 */
|
||||
private static final Map<String, Object> CONSTS = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 工具类构造器,防止实例化。
|
||||
*/
|
||||
private GlobalConstTable() { /* utility class */ }
|
||||
|
||||
/**
|
||||
* 注册一个全局常量到表中(只在首次注册时生效,避免被覆盖)。
|
||||
*
|
||||
* @param qualifiedName 常量的全限定名(如 "ModuleA.a")
|
||||
* @param value 常量的字面值(如 10、字符串、布尔等)
|
||||
* @throws IllegalArgumentException 名称为 null 或空串时抛出
|
||||
*/
|
||||
public static void register(String qualifiedName, Object value) {
|
||||
if (qualifiedName == null || qualifiedName.isBlank()) {
|
||||
throw new IllegalArgumentException("常量名不能为空");
|
||||
}
|
||||
CONSTS.putIfAbsent(qualifiedName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定全局常量的值。
|
||||
*
|
||||
* @param qualifiedName 常量的全限定名(如 "ModuleA.a")
|
||||
* @return 查到的常量值,如果未注册则返回 null
|
||||
*/
|
||||
public static Object get(String qualifiedName) {
|
||||
return CONSTS.get(qualifiedName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回全部已注册常量的不可变视图(快照)。
|
||||
* <p>注意:只读,不可修改。</p>
|
||||
*
|
||||
* @return key=常量名,value=常量值的不可变 Map
|
||||
*/
|
||||
public static Map<String, Object> all() {
|
||||
return Map.copyOf(CONSTS);
|
||||
}
|
||||
}
|
||||
@ -35,11 +35,6 @@ public class IRFunction {
|
||||
*/
|
||||
private final List<IRVirtualRegister> parameters = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 正式参数所对应的类型,按声明顺序排列。
|
||||
*/
|
||||
private final List<String> parametersType = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 构造一个具有指定名称的 IRFunction 实例。
|
||||
*
|
||||
@ -66,15 +61,13 @@ public class IRFunction {
|
||||
* </p>
|
||||
*
|
||||
* @param vr 表示函数某个参数的虚拟寄存器
|
||||
* @param type 表示函数某个参数的类型
|
||||
*/
|
||||
public void addParameter(IRVirtualRegister vr, String type) {
|
||||
public void addParameter(IRVirtualRegister vr) {
|
||||
parameters.add(vr);
|
||||
parametersType.add(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取函数正式参数的虚拟寄存器只读列表。
|
||||
* 获取函数正式参数的只读列表。
|
||||
*
|
||||
* @return 按声明顺序排列的虚拟寄存器列表
|
||||
*/
|
||||
@ -82,15 +75,6 @@ public class IRFunction {
|
||||
return List.copyOf(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取函数正式参数的类型只读列表。
|
||||
*
|
||||
* @return 按声明顺序排列的类型列表
|
||||
*/
|
||||
public List<String> parametersType() {
|
||||
return List.copyOf(parametersType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向函数体末尾追加一条 IR 指令。
|
||||
*
|
||||
@ -146,7 +130,6 @@ public class IRFunction {
|
||||
.append('(');
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
sb.append(parameters.get(i));
|
||||
sb.append(": ").append(parametersType.get(i));
|
||||
if (i < parameters.size() - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ public final class ExpressionUtils {
|
||||
/**
|
||||
* 设置当前线程的默认类型后缀。
|
||||
*
|
||||
* @param suffix 类型后缀字符(b/s/l/f),'\0'表示无
|
||||
* @param suffix 类型后缀字符(b/s/i/l/f/d),'\0'表示无
|
||||
*/
|
||||
public static void setDefaultSuffix(char suffix) { DEFAULT_SUFFIX.set(suffix); }
|
||||
|
||||
@ -47,21 +47,21 @@ public final class ExpressionUtils {
|
||||
// ───────────── 字面量常量解析 ─────────────
|
||||
|
||||
/**
|
||||
* 安全解析整数字面量字符串,自动去除单字符类型后缀(b/s/l/f,大小写均可),并转换为 int。
|
||||
* 安全解析整数字面量字符串,自动去除单字符类型后缀(b/s/l/f/d,大小写均可),并转换为 int。
|
||||
*
|
||||
* @param literal 字面量字符串
|
||||
* @return 字面量对应的 int 数值
|
||||
* @throws NumberFormatException 如果字面量无法转换为整数
|
||||
*/
|
||||
public static int parseIntSafely(String literal) {
|
||||
String digits = literal.replaceAll("[bslfBSLF]$", "");
|
||||
String digits = literal.replaceAll("[bslfdBSDLF]$", "");
|
||||
return Integer.parseInt(digits);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数字字面量字符串推断类型并生成对应的 IRConstant 常量值。
|
||||
* <p>
|
||||
* 支持的字面量后缀有 b/s/l/f(大小写均可)。
|
||||
* 支持的字面量后缀有 b/s/l/f/d(大小写均可)。
|
||||
* 无后缀时,优先参考 IRContext 当前变量类型,否则根据字面量格式(含'.'或'e'等)判断为 double,否则为 int。
|
||||
*
|
||||
* @param ctx IRContext,允许参考变量声明类型
|
||||
@ -73,7 +73,7 @@ public final class ExpressionUtils {
|
||||
: Character.toLowerCase(value.charAt(value.length() - 1));
|
||||
|
||||
String digits = switch (suffix) {
|
||||
case 'b','s','l','f' -> value.substring(0, value.length() - 1);
|
||||
case 'b','s','l','f','d' -> value.substring(0, value.length() - 1);
|
||||
default -> {
|
||||
// 无后缀,优先参考变量类型
|
||||
if (ctx.getVarType() != null) {
|
||||
@ -249,7 +249,7 @@ public final class ExpressionUtils {
|
||||
* @param digits 字面量字符串
|
||||
* @return 是浮点格式则返回 true
|
||||
*/
|
||||
public static boolean looksLikeFloat(String digits) {
|
||||
private static boolean looksLikeFloat(String digits) {
|
||||
return digits.indexOf('.') >= 0
|
||||
|| digits.indexOf('e') >= 0
|
||||
|| digits.indexOf('E') >= 0;
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
package org.jcnc.snow.compiler.lexer.core;
|
||||
|
||||
import org.jcnc.snow.common.SnowConfig;
|
||||
import org.jcnc.snow.compiler.lexer.base.TokenScanner;
|
||||
import org.jcnc.snow.compiler.lexer.scanners.*;
|
||||
import org.jcnc.snow.compiler.lexer.token.Token;
|
||||
import org.jcnc.snow.compiler.lexer.token.TokenType;
|
||||
import org.jcnc.snow.compiler.lexer.utils.TokenPrinter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@ -56,9 +54,9 @@ public class LexerEngine {
|
||||
/* 2. 后置整体校验 */
|
||||
validateTokens();
|
||||
/* 3. 打印 token */
|
||||
if (SnowConfig.isDebug()) {
|
||||
TokenPrinter.print(tokens);
|
||||
}
|
||||
// if (SnowConfig.isDebug()) {
|
||||
// TokenPrinter.print(tokens);
|
||||
// }
|
||||
|
||||
/* 4. 统一报告错误 */
|
||||
report(errors);
|
||||
@ -127,8 +125,8 @@ public class LexerEngine {
|
||||
|
||||
/**
|
||||
* 目前包含2条规则: <br>
|
||||
* 1. Declare-Ident declare 后必须紧跟合法标识符,并且只能一个<br>
|
||||
* 2. Double-Ident declare 后若出现第二个 IDENTIFIER 视为多余<br>
|
||||
* 1. Declare-Ident declare 后必须紧跟合法标识符(或 const + 标识符),并且只能一个<br>
|
||||
* 2. Double-Ident declare 后若出现第二个多余的 IDENTIFIER<br>
|
||||
* <p>发现问题仅写入 {@link #errors},不抛异常。</p>
|
||||
*/
|
||||
private void validateTokens() {
|
||||
@ -139,17 +137,28 @@ public class LexerEngine {
|
||||
if (tok.getType() == TokenType.KEYWORD
|
||||
&& "declare".equalsIgnoreCase(tok.getLexeme())) {
|
||||
|
||||
// 第一个非 NEWLINE token
|
||||
Token id1 = findNextNonNewline(i);
|
||||
// 找 declare 后第一个非 NEWLINE token
|
||||
Token t1 = findNextNonNewline(i);
|
||||
|
||||
// 如果有 const,允许
|
||||
boolean hasConst = t1 != null
|
||||
&& t1.getType() == TokenType.KEYWORD
|
||||
&& "const".equalsIgnoreCase(t1.getLexeme());
|
||||
int identStartIdx = hasConst ? tokens.indexOf(t1) : i;
|
||||
|
||||
// 找下一个非 NEWLINE token,如果有 const,就找下一个
|
||||
Token id1 = findNextNonNewline(identStartIdx);
|
||||
|
||||
// id1 必须是 IDENTIFIER
|
||||
if (id1 == null || id1.getType() != TokenType.IDENTIFIER) {
|
||||
errors.add(err(
|
||||
(id1 == null ? tok : id1),
|
||||
"declare 后必须跟合法标识符 (以字母或 '_' 开头)"
|
||||
(id1 == null ? (hasConst ? t1 : tok) : id1),
|
||||
"declare 后必须跟合法标识符 (可选 const 关键字)"
|
||||
));
|
||||
continue; // 若首标识符就错,后续检查可略
|
||||
}
|
||||
|
||||
// 检查是否有第二个 IDENTIFIER
|
||||
// 检查是否有第二个多余的 IDENTIFIER
|
||||
Token id2 = findNextNonNewline(tokens.indexOf(id1));
|
||||
if (id2 != null && id2.getType() == TokenType.IDENTIFIER) {
|
||||
errors.add(err(id2, "declare 声明中出现多余的标识符"));
|
||||
|
||||
@ -28,7 +28,7 @@ public class TokenFactory {
|
||||
private static final Set<String> KEYWORDS = Set.of
|
||||
("module", "function", "params", "returns", "body", "end",
|
||||
"if", "then", "else", "loop", "declare", "return", "import", "init",
|
||||
"cond", "step", "globals", "break", "continue");
|
||||
"cond", "step", "globals", "break", "continue", "const");
|
||||
|
||||
/**
|
||||
* 内置类型名称集合,如 int、string 等。
|
||||
|
||||
@ -10,21 +10,26 @@ import java.util.Optional;
|
||||
* {@code DeclarationNode} 表示抽象语法树(AST)中的变量声明语句节点。
|
||||
* <p>
|
||||
* 变量声明用于在语法层引入新的标识符及其类型信息,
|
||||
* 通常格式为 {@code type name = initializer;},其中初始化表达式可省略。
|
||||
* </p>
|
||||
*/
|
||||
public class DeclarationNode implements StatementNode {
|
||||
|
||||
/** 声明的变量名称 */
|
||||
/** 声明的变量名称。 */
|
||||
private final String name;
|
||||
|
||||
/** 变量的数据类型(如 "int", "string") */
|
||||
/** 变量的数据类型(如 "int", "string")。 */
|
||||
private final String type;
|
||||
|
||||
/** 可选的初始化表达式 */
|
||||
/** 是否为常量声明(true 表示 const 变量,false 表示普通变量)。 */
|
||||
private final boolean isConst;
|
||||
|
||||
/**
|
||||
* 可选的初始化表达式。
|
||||
* 如果未指定初始化表达式,则为 Optional.empty()。
|
||||
*/
|
||||
private final Optional<ExpressionNode> initializer;
|
||||
|
||||
/** 节点上下文信息(包含行号、列号等) */
|
||||
/** 节点上下文信息(如源码中的行号、列号等)。 */
|
||||
private final NodeContext context;
|
||||
|
||||
/**
|
||||
@ -32,12 +37,14 @@ public class DeclarationNode implements StatementNode {
|
||||
*
|
||||
* @param name 变量名称
|
||||
* @param type 变量类型字符串(如 "int"、"string")
|
||||
* @param isConst 是否为常量声明
|
||||
* @param initializer 可选初始化表达式,若为 {@code null} 表示未初始化
|
||||
* @param context 节点上下文信息(包含行号、列号等)
|
||||
*/
|
||||
public DeclarationNode(String name, String type, ExpressionNode initializer, NodeContext context) {
|
||||
public DeclarationNode(String name, String type, boolean isConst, ExpressionNode initializer, NodeContext context) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.isConst = isConst;
|
||||
this.initializer = Optional.ofNullable(initializer);
|
||||
this.context = context;
|
||||
}
|
||||
@ -60,6 +67,15 @@ public class DeclarationNode implements StatementNode {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断该声明是否为常量(const)。
|
||||
*
|
||||
* @return 如果为常量声明则返回 true,否则返回 false
|
||||
*/
|
||||
public boolean isConst() {
|
||||
return isConst;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可选的初始化表达式。
|
||||
*
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jcnc.snow.compiler.parser.expression;
|
||||
|
||||
import org.jcnc.snow.common.StringEscape;
|
||||
import org.jcnc.snow.compiler.lexer.token.Token;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.StringLiteralNode;
|
||||
@ -26,8 +27,14 @@ public class StringLiteralParselet implements PrefixParselet {
|
||||
*/
|
||||
@Override
|
||||
public ExpressionNode parse(ParserContext ctx, Token token) {
|
||||
// 去除首尾引号
|
||||
String raw = token.getRaw();
|
||||
String content = raw.substring(1, raw.length() - 1);
|
||||
return new StringLiteralNode(content, new NodeContext(token.getLine(), token.getCol(), ctx.getSourceName()));
|
||||
String inner = raw.substring(1, raw.length() - 1);
|
||||
// 解析转义符与 Unicode 转义
|
||||
String value = StringEscape.unescape(inner);
|
||||
return new StringLiteralNode(
|
||||
value,
|
||||
new NodeContext(token.getLine(), token.getCol(), ctx.getSourceName())
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -34,45 +34,51 @@ public class DeclarationStatementParser implements StatementParser {
|
||||
*/
|
||||
@Override
|
||||
public DeclarationNode parse(ParserContext ctx) {
|
||||
// 便捷引用词法 token 流
|
||||
var tokens = ctx.getTokens();
|
||||
|
||||
// 获取当前 token 的行号、列号和文件名
|
||||
int line = ctx.getTokens().peek().getLine();
|
||||
int column = ctx.getTokens().peek().getCol();
|
||||
int line = tokens.peek().getLine();
|
||||
int column = tokens.peek().getCol();
|
||||
String file = ctx.getSourceName();
|
||||
|
||||
// 声明语句必须以 "declare" 开头
|
||||
ctx.getTokens().expect("declare");
|
||||
tokens.expect("declare");
|
||||
|
||||
// 是否声明为常量
|
||||
boolean isConst = tokens.match("const");
|
||||
|
||||
// 获取变量名称(标识符)
|
||||
String name = ctx.getTokens()
|
||||
String name = tokens
|
||||
.expectType(TokenType.IDENTIFIER)
|
||||
.getLexeme();
|
||||
|
||||
// 类型标注的冒号分隔符
|
||||
ctx.getTokens().expect(":");
|
||||
tokens.expect(":");
|
||||
|
||||
// 获取变量类型(类型标识符)
|
||||
StringBuilder type = new StringBuilder(
|
||||
ctx.getTokens()
|
||||
tokens
|
||||
.expectType(TokenType.TYPE)
|
||||
.getLexeme()
|
||||
);
|
||||
|
||||
// 消费多维数组类型后缀 "[]"
|
||||
while (ctx.getTokens().match("[")) {
|
||||
ctx.getTokens().expectType(TokenType.RBRACKET); // 必须配对
|
||||
while (tokens.match("[")) {
|
||||
tokens.expectType(TokenType.RBRACKET); // 必须配对
|
||||
type.append("[]"); // 类型名称拼接 [],如 int[][] 等
|
||||
}
|
||||
|
||||
// 可选初始化表达式(=号右侧)
|
||||
ExpressionNode init = null;
|
||||
if (ctx.getTokens().match("=")) {
|
||||
if (tokens.match("=")) {
|
||||
init = new PrattExpressionParser().parse(ctx);
|
||||
}
|
||||
|
||||
// 声明语句必须以换行符结尾
|
||||
ctx.getTokens().expectType(TokenType.NEWLINE);
|
||||
tokens.expectType(TokenType.NEWLINE);
|
||||
|
||||
// 返回构建好的声明语法树节点
|
||||
return new DeclarationNode(name, type.toString(), init, new NodeContext(line, column, file));
|
||||
return new DeclarationNode(name, type.toString(), isConst, init, new NodeContext(line, column, file));
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import java.util.List;
|
||||
* <li>支持数值参数的宽化转换(如 int → double);</li>
|
||||
* <li>支持数值到字符串的隐式转换(自动视为调用 {@code to_string});</li>
|
||||
* <li>在发生类型不匹配、未导入模块或函数未定义等情况下记录语义错误。</li>
|
||||
* <li>新增:以"_"开头的函数名只允许在本模块访问,禁止跨模块访问。</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CallExpressionAnalyzer implements ExpressionAnalyzer<CallExpressionNode> {
|
||||
@ -64,19 +65,42 @@ public class CallExpressionAnalyzer implements ExpressionAnalyzer<CallExpression
|
||||
}
|
||||
target = ctx.getModules().get(mod);
|
||||
functionName = member;
|
||||
|
||||
}
|
||||
// 简单函数名形式: func(...)
|
||||
} else if (callee instanceof IdentifierNode(String name, NodeContext _)) {
|
||||
else if (callee instanceof IdentifierNode(String name, NodeContext _)) {
|
||||
functionName = name;
|
||||
|
||||
}
|
||||
// 不支持的 callee 形式
|
||||
} else {
|
||||
else {
|
||||
ctx.getErrors().add(new SemanticError(callee,
|
||||
"不支持的调用方式: " + callee));
|
||||
ctx.log("错误: 不支持的调用方式 " + callee);
|
||||
return BuiltinType.INT;
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// 私有函数访问控制
|
||||
// -------------------------
|
||||
// 如果函数名以"_"开头,且不是在本模块调用,则报错
|
||||
if (functionName.startsWith("_") && !target.getName().equals(mi.getName())) {
|
||||
ctx.getErrors().add(new SemanticError(callee,
|
||||
"无法访问模块私有函数: " + target.getName() + "." + functionName
|
||||
+ "(下划线开头的函数只允许在定义模块内访问)"));
|
||||
ctx.log("错误: 试图跨模块访问私有函数 " + target.getName() + "." + functionName);
|
||||
return BuiltinType.INT;
|
||||
}
|
||||
|
||||
// 查找目标函数签名(先在当前模块/指定模块查找)
|
||||
FunctionType ft = target.getFunctions().get(functionName);
|
||||
|
||||
// 未找到则报错
|
||||
if (ft == null) {
|
||||
ctx.getErrors().add(new SemanticError(callee,
|
||||
"函数未定义: " + functionName));
|
||||
ctx.log("错误: 函数未定义 " + functionName);
|
||||
return BuiltinType.INT;
|
||||
}
|
||||
|
||||
// 分析所有实参并获取类型
|
||||
List<Type> args = new ArrayList<>();
|
||||
for (ExpressionNode arg : call.arguments()) {
|
||||
@ -84,17 +108,6 @@ public class CallExpressionAnalyzer implements ExpressionAnalyzer<CallExpression
|
||||
.analyze(ctx, mi, fn, locals, arg));
|
||||
}
|
||||
|
||||
// 查找目标函数签名(先在当前模块/显式模块查找)
|
||||
FunctionType ft = target.retrieveFunction(functionName, args);
|
||||
|
||||
// 未找到则报错
|
||||
if (ft == null) {
|
||||
ctx.getErrors().add(new SemanticError(callee,
|
||||
"函数未定义: " + functionName));
|
||||
ctx.log("错误: 函数未定义 " + functionName);
|
||||
return BuiltinType.INT;
|
||||
}
|
||||
|
||||
// 参数数量检查
|
||||
if (args.size() != ft.paramTypes().size()) {
|
||||
ctx.getErrors().add(new SemanticError(call,
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
package org.jcnc.snow.compiler.semantic.analyzers.expression;
|
||||
|
||||
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.IdentifierNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.MemberExpressionNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.base.NodeContext;
|
||||
import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer;
|
||||
import org.jcnc.snow.compiler.semantic.core.Context;
|
||||
import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
|
||||
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.Symbol;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
|
||||
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
|
||||
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||
|
||||
/**
|
||||
* {@code MemberExpressionAnalyzer} 用于分析模块成员访问表达式的类型和语义。
|
||||
*
|
||||
* <p>
|
||||
* 当前实现支持 <code>ModuleName.constOrVar</code> 形式的跨模块常量/全局变量访问,
|
||||
* 能根据目标模块的全局符号表,返回准确的类型信息,完全支持跨模块类型推断。
|
||||
* <br>
|
||||
* 对于非模块成员的访问(如对象.属性、多级 a.b.c),暂不支持,遇到时将报告语义错误。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <b>核心特性:</b>
|
||||
* <ul>
|
||||
* <li>校验模块是否存在、是否已导入(或自身);</li>
|
||||
* <li>跨模块访问目标模块的全局符号表,查找指定成员符号及其类型;</li>
|
||||
* <li>若成员不存在,报告“模块成员未定义”语义错误;</li>
|
||||
* <li>暂不支持更复杂的对象成员访问,遇到将报“不支持的成员访问对象类型”错误。</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public class MemberExpressionAnalyzer implements ExpressionAnalyzer<MemberExpressionNode> {
|
||||
|
||||
/**
|
||||
* 语义分析模块成员访问表达式。
|
||||
*
|
||||
* @param ctx 全局语义分析上下文,持有所有模块及错误记录
|
||||
* @param mi 当前模块信息(用于判断导入关系)
|
||||
* @param fn 当前函数节点
|
||||
* @param locals 当前局部符号表
|
||||
* @param expr 当前要分析的成员表达式(如 ModuleA.a)
|
||||
* @return 成员表达式的类型;出错时类型降级为 int,并记录语义错误
|
||||
*/
|
||||
@Override
|
||||
public Type analyze(Context ctx,
|
||||
ModuleInfo mi,
|
||||
FunctionNode fn,
|
||||
SymbolTable locals,
|
||||
MemberExpressionNode expr) {
|
||||
|
||||
ctx.log("检查成员访问: " + expr);
|
||||
|
||||
// -------- 仅支持 ModuleName.member 形式 --------
|
||||
if (expr.object() instanceof IdentifierNode(String mod, NodeContext _)) {
|
||||
|
||||
// 1. 校验模块存在且已导入或为本模块自身
|
||||
if (!ctx.getModules().containsKey(mod)
|
||||
|| (!mi.getImports().contains(mod) && !mi.getName().equals(mod))) {
|
||||
ctx.getErrors().add(new SemanticError(expr,
|
||||
"未知或未导入模块: " + mod));
|
||||
ctx.log("错误: 未导入模块 " + mod);
|
||||
return BuiltinType.INT;
|
||||
}
|
||||
|
||||
// 2. 查找目标模块的全局符号表,精确返回成员类型
|
||||
ModuleInfo target = ctx.getModules().get(mod);
|
||||
SymbolTable globals = target.getGlobals();
|
||||
if (globals != null) {
|
||||
Symbol sym = globals.resolve(expr.member());
|
||||
if (sym != null) {
|
||||
return sym.type(); // 找到成员,返回其真实类型
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 成员不存在,记录语义错误并类型降级
|
||||
ctx.getErrors().add(new SemanticError(expr,
|
||||
"模块成员未定义: " + mod + "." + expr.member()));
|
||||
ctx.log("错误: 模块成员未定义 " + mod + "." + expr.member());
|
||||
return BuiltinType.INT;
|
||||
}
|
||||
|
||||
// -------- 其它对象成员(如 a.b.c)暂不支持 --------
|
||||
ctx.getErrors().add(new SemanticError(expr,
|
||||
"不支持的成员访问对象类型: "
|
||||
+ expr.object().getClass().getSimpleName()));
|
||||
ctx.log("错误: 不支持的成员访问对象类型 "
|
||||
+ expr.object().getClass().getSimpleName());
|
||||
return BuiltinType.INT;
|
||||
}
|
||||
}
|
||||
@ -6,22 +6,28 @@ import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
||||
import org.jcnc.snow.compiler.semantic.core.Context;
|
||||
import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
|
||||
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.*;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.Symbol;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.SymbolKind;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
|
||||
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||
|
||||
/**
|
||||
* {@code AssignmentAnalyzer} 是赋值语句的语义分析器。
|
||||
* <p>
|
||||
* 负责分析和验证赋值语句的合法性,包括:
|
||||
* {@code AssignmentAnalyzer} 负责对赋值语句进行语义校验。
|
||||
*
|
||||
* <h2>校验要点</h2>
|
||||
* <ul>
|
||||
* <li>变量是否已声明且可赋值(必须为 {@link SymbolKind#VARIABLE} 类型);</li>
|
||||
* <li>赋值右值的类型是否与变量类型兼容;</li>
|
||||
* <li>是否允许进行数值类型的自动宽化转换(如 {@code int → float})。</li>
|
||||
* <li><b>左值解析</b>:确保标识符已声明;</li>
|
||||
* <li><b>常量保护</b>:禁止修改 {@link SymbolKind#CONSTANT};</li>
|
||||
* <li><b>类型兼容</b>:验证右值类型与目标类型兼容,若均为数值类型则允许宽化转换(如 <code>int → double</code>)。</li>
|
||||
* </ul>
|
||||
* 若类型不兼容且无法自动宽化,则将记录语义错误并输出日志信息。
|
||||
*
|
||||
* 任何不满足条件的情况都会向 {@link Context#getErrors()} 记录 {@link SemanticError}。
|
||||
*/
|
||||
public class AssignmentAnalyzer implements StatementAnalyzer<AssignmentNode> {
|
||||
|
||||
/** 错误消息前缀,统一便于搜索定位 */
|
||||
private static final String ERR_PREFIX = "赋值错误: ";
|
||||
|
||||
/**
|
||||
* 分析赋值语句的语义有效性,包括左值合法性与类型匹配性。
|
||||
*
|
||||
@ -41,29 +47,37 @@ public class AssignmentAnalyzer implements StatementAnalyzer<AssignmentNode> {
|
||||
// 获取赋值左值变量名并进行符号解析
|
||||
ctx.log("赋值检查: " + asg.variable());
|
||||
Symbol sym = locals.resolve(asg.variable());
|
||||
|
||||
// 检查变量是否已声明且为可赋值的变量类型
|
||||
if (sym == null || sym.kind() != SymbolKind.VARIABLE) {
|
||||
if (sym == null) {
|
||||
ctx.getErrors().add(new SemanticError(asg,
|
||||
"未声明的变量: " + asg.variable()));
|
||||
ctx.log("错误: 未声明的变量 " + asg.variable());
|
||||
ERR_PREFIX + "未声明的变量: " + asg.variable()));
|
||||
ctx.log(ERR_PREFIX + "未声明的变量 " + asg.variable());
|
||||
return; // 无需继续后续检查
|
||||
}
|
||||
|
||||
/* ---------- 2. 常量不可修改 ---------- */
|
||||
if (sym.kind() == SymbolKind.CONSTANT) {
|
||||
ctx.getErrors().add(new SemanticError(asg,
|
||||
ERR_PREFIX + "无法修改常量: " + asg.variable()));
|
||||
ctx.log(ERR_PREFIX + "尝试修改常量 " + asg.variable());
|
||||
return;
|
||||
}
|
||||
|
||||
// 分析右值表达式类型
|
||||
Type valType = ctx.getRegistry().getExpressionAnalyzer(asg.value())
|
||||
/* ---------- 3. 右值类型分析 ---------- */
|
||||
Type rhsType = ctx.getRegistry()
|
||||
.getExpressionAnalyzer(asg.value())
|
||||
.analyze(ctx, mi, fn, locals, asg.value());
|
||||
|
||||
// 类型检查: 若类型不兼容,则尝试判断是否允许宽化转换
|
||||
if (!sym.type().isCompatible(valType)) {
|
||||
// 数值类型允许自动宽化转换(如 int → double)
|
||||
if (!(sym.type().isNumeric() && valType.isNumeric()
|
||||
&& Type.widen(valType, sym.type()) == sym.type())) {
|
||||
/* ---------- 4. 类型兼容性检查 ---------- */
|
||||
boolean compatible = sym.type().isCompatible(rhsType);
|
||||
boolean widenOK = sym.type().isNumeric()
|
||||
&& rhsType.isNumeric()
|
||||
&& Type.widen(rhsType, sym.type()) == sym.type();
|
||||
|
||||
if (!compatible && !widenOK) {
|
||||
ctx.getErrors().add(new SemanticError(asg,
|
||||
"赋值类型不匹配: 期望 " + sym.type()
|
||||
+ ", 实际 " + valType));
|
||||
ctx.log("错误: 赋值类型不匹配 " + asg.variable());
|
||||
}
|
||||
ERR_PREFIX + "类型不匹配: 期望 "
|
||||
+ sym.type() + ", 实际 " + rhsType));
|
||||
ctx.log(ERR_PREFIX + "类型不匹配 " + asg.variable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,9 @@ import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
||||
import org.jcnc.snow.compiler.semantic.core.Context;
|
||||
import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
|
||||
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.*;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.Symbol;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.SymbolKind;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
|
||||
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
|
||||
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||
|
||||
@ -25,7 +27,7 @@ import org.jcnc.snow.compiler.semantic.type.Type;
|
||||
public class DeclarationAnalyzer implements StatementAnalyzer<DeclarationNode> {
|
||||
|
||||
/**
|
||||
* 对变量声明语句执行语义分析。
|
||||
* 对单条声明语句执行语义分析。
|
||||
*
|
||||
* @param ctx 当前语义分析上下文对象,提供类型解析、错误记录、日志输出等功能。
|
||||
* @param mi 当前模块信息,支持跨模块引用检查(本分析器未直接使用)。
|
||||
@ -40,45 +42,53 @@ public class DeclarationAnalyzer implements StatementAnalyzer<DeclarationNode> {
|
||||
SymbolTable locals,
|
||||
DeclarationNode decl) {
|
||||
|
||||
// 1. 解析声明类型
|
||||
/* ---------- 1. 解析类型 ---------- */
|
||||
Type varType = ctx.parseType(decl.getType());
|
||||
if (varType == null) {
|
||||
// 未知类型:记录错误并使用 int 兜底,避免后续空指针
|
||||
ctx.getErrors().add(new SemanticError(decl,
|
||||
"未知类型: " + decl.getType()));
|
||||
ctx.log("错误: 未知类型 " + decl.getType()
|
||||
+ " 在声明 " + decl.getName());
|
||||
varType = BuiltinType.INT; // 容错处理: 默认降级为 int
|
||||
+ " (声明 " + decl.getName() + ")");
|
||||
varType = BuiltinType.INT;
|
||||
}
|
||||
ctx.log("声明变量: " + decl.getName()
|
||||
+ " 类型: " + varType);
|
||||
ctx.log("声明" + (decl.isConst() ? "常量" : "变量")
|
||||
+ ": " + decl.getName() + " 类型: " + varType);
|
||||
|
||||
// 2. 将变量注册到当前作用域符号表中,检查重复定义
|
||||
if (!locals.define(new Symbol(
|
||||
decl.getName(), varType, SymbolKind.VARIABLE
|
||||
))) {
|
||||
/* ---------- 2. 常量必须初始化 ---------- */
|
||||
if (decl.isConst() && decl.getInitializer().isEmpty()) {
|
||||
ctx.getErrors().add(new SemanticError(decl,
|
||||
"变量重复声明: " + decl.getName()));
|
||||
ctx.log("错误: 变量重复声明 " + decl.getName());
|
||||
"常量必须在声明时初始化: " + decl.getName()));
|
||||
// 继续分析以捕获更多错误
|
||||
}
|
||||
|
||||
// 3. 检查初始化表达式(如果存在)
|
||||
Type finalVarType = varType; // 用于 lambda 捕获
|
||||
decl.getInitializer().ifPresent(init -> {
|
||||
Type initType = ctx.getRegistry().getExpressionAnalyzer(init)
|
||||
.analyze(ctx, mi, fn, locals, init);
|
||||
/* ---------- 3. 注册符号并检测重名 ---------- */
|
||||
SymbolKind kind = decl.isConst() ? SymbolKind.CONSTANT
|
||||
: SymbolKind.VARIABLE;
|
||||
if (!locals.define(new Symbol(decl.getName(), varType, kind))) {
|
||||
ctx.getErrors().add(new SemanticError(decl,
|
||||
"重复声明: " + decl.getName()));
|
||||
ctx.log("错误: 重复声明 " + decl.getName());
|
||||
}
|
||||
|
||||
// 检查类型是否兼容,或是否允许数值宽化转换
|
||||
if (!finalVarType.isCompatible(initType)) {
|
||||
boolean canWiden = finalVarType.isNumeric()
|
||||
/* ---------- 4. 校验初始化表达式(若存在) ---------- */
|
||||
Type finalVarType = varType;
|
||||
decl.getInitializer().ifPresent(initExpr -> {
|
||||
// 4.1 获取初始化表达式类型
|
||||
Type initType = ctx.getRegistry()
|
||||
.getExpressionAnalyzer(initExpr)
|
||||
.analyze(ctx, mi, fn, locals, initExpr);
|
||||
|
||||
// 4.2 类型兼容性检查 + 数值宽化
|
||||
boolean compatible = finalVarType.isCompatible(initType);
|
||||
boolean widenOK = finalVarType.isNumeric()
|
||||
&& initType.isNumeric()
|
||||
&& Type.widen(initType, finalVarType) == finalVarType;
|
||||
if (!canWiden) {
|
||||
|
||||
if (!compatible && !widenOK) {
|
||||
ctx.getErrors().add(new SemanticError(decl,
|
||||
"初始化类型不匹配: 期望 " + finalVarType
|
||||
+ ", 实际 " + initType));
|
||||
ctx.log("错误: 初始化类型不匹配 "
|
||||
+ decl.getName());
|
||||
}
|
||||
"初始化类型不匹配: 期望 " + finalVarType + ", 实际 " + initType));
|
||||
ctx.log("错误: 初始化类型不匹配 " + decl.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package org.jcnc.snow.compiler.semantic.analyzers.statement;
|
||||
|
||||
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.ParameterNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.ReturnNode;
|
||||
import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
||||
import org.jcnc.snow.compiler.semantic.core.Context;
|
||||
@ -12,9 +11,6 @@ import org.jcnc.snow.compiler.semantic.type.BuiltinType;
|
||||
import org.jcnc.snow.compiler.semantic.type.FunctionType;
|
||||
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@code ReturnAnalyzer} 是用于分析 {@link ReturnNode} 返回语句的语义分析器。
|
||||
* <p>
|
||||
@ -46,32 +42,12 @@ public class ReturnAnalyzer implements StatementAnalyzer<ReturnNode> {
|
||||
|
||||
ctx.log("检查 return");
|
||||
|
||||
// 获取当前函数的重载列表
|
||||
var overloading = ctx.getModules()
|
||||
// 获取当前函数的定义信息
|
||||
FunctionType expected = ctx.getModules()
|
||||
.get(mi.getName())
|
||||
.getFunctions()
|
||||
.get(fn.name());
|
||||
|
||||
List<Type> params = new ArrayList<>();
|
||||
for (ParameterNode pn : fn.parameters()) {
|
||||
params.add(locals.resolve(pn.name()).type());
|
||||
}
|
||||
|
||||
// fn 对应的函数签名
|
||||
FunctionType expected = overloading.stream()
|
||||
.filter(ft -> ft.paramTypes().equals(params))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (expected == null) {
|
||||
ctx.getErrors().add(new SemanticError(
|
||||
fn,
|
||||
"不存在的函数签名: " + params
|
||||
));
|
||||
ctx.log("不存在的函数签名: " + params);
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况 1: 存在返回表达式,需进行类型检查
|
||||
ret.getExpression().ifPresentOrElse(exp -> {
|
||||
var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(exp);
|
||||
|
||||
@ -65,8 +65,8 @@ public final class AnalyzerRegistrar {
|
||||
// ---------- 注册一元表达式分析器 ----------
|
||||
registry.registerExpressionAnalyzer(UnaryExpressionNode.class, new UnaryExpressionAnalyzer());
|
||||
|
||||
// 对尚未实现的表达式类型使用兜底处理器
|
||||
// ---------- 成员访问表达式 ----------
|
||||
registry.registerExpressionAnalyzer(MemberExpressionNode.class,
|
||||
new UnsupportedExpressionAnalyzer<>());
|
||||
new MemberExpressionAnalyzer());
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,54 +62,14 @@ public final class BuiltinTypeRegistry {
|
||||
/* ---------- 注册标准库 os ---------- */
|
||||
ModuleInfo utils = new ModuleInfo("os");
|
||||
|
||||
/* syscall(string, type): void 的一组重载 */
|
||||
List<FunctionType> overloading = new ArrayList<>();
|
||||
{
|
||||
// syscall(string, byte): void
|
||||
overloading.add(new FunctionType(
|
||||
Arrays.asList(BuiltinType.STRING, BuiltinType.BYTE),
|
||||
BuiltinType.VOID
|
||||
));
|
||||
|
||||
// syscall(string, short): void
|
||||
overloading.add(new FunctionType(
|
||||
Arrays.asList(BuiltinType.STRING, BuiltinType.SHORT),
|
||||
BuiltinType.VOID
|
||||
));
|
||||
|
||||
// syscall(string, int): void
|
||||
overloading.add(new FunctionType(
|
||||
// syscall(string, int): void —— 供标准库内部使用的调用接口
|
||||
utils.getFunctions().put(
|
||||
"syscall",
|
||||
new FunctionType(
|
||||
Arrays.asList(BuiltinType.STRING, BuiltinType.INT),
|
||||
BuiltinType.VOID
|
||||
));
|
||||
|
||||
// syscall(string, long): void
|
||||
overloading.add(new FunctionType(
|
||||
Arrays.asList(BuiltinType.STRING, BuiltinType.LONG),
|
||||
BuiltinType.VOID
|
||||
));
|
||||
|
||||
// syscall(string, float): void
|
||||
overloading.add(new FunctionType(
|
||||
Arrays.asList(BuiltinType.STRING, BuiltinType.FLOAT),
|
||||
BuiltinType.VOID
|
||||
));
|
||||
|
||||
// syscall(string, double): void
|
||||
overloading.add(new FunctionType(
|
||||
Arrays.asList(BuiltinType.STRING, BuiltinType.DOUBLE),
|
||||
BuiltinType.VOID
|
||||
));
|
||||
|
||||
// syscall(string, boolean): void
|
||||
overloading.add(new FunctionType(
|
||||
Arrays.asList(BuiltinType.STRING, BuiltinType.BOOLEAN),
|
||||
BuiltinType.VOID
|
||||
));
|
||||
}
|
||||
|
||||
// syscall(string, `type`): void —— 供标准库内部使用的调用接口
|
||||
utils.getFunctions().put("syscall", overloading);
|
||||
)
|
||||
);
|
||||
|
||||
// 注册 BuiltinUtils 到上下文的模块表(若已存在则不重复添加)
|
||||
ctx.getModules().putIfAbsent("os", utils);
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
package org.jcnc.snow.compiler.semantic.core;
|
||||
|
||||
import org.jcnc.snow.compiler.parser.ast.DeclarationNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.ModuleNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.DeclarationNode;
|
||||
import org.jcnc.snow.compiler.parser.ast.ReturnNode;
|
||||
import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
||||
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
||||
@ -11,70 +11,85 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolKind;
|
||||
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
|
||||
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@code FunctionChecker} 是语义分析阶段中用于检查函数体语句合法性的调度器。
|
||||
* {@code FunctionChecker} 是 Snow 编译器语义分析阶段用于检查所有函数体合法性的总控调度器。
|
||||
* <p>
|
||||
* 它逐个遍历所有模块中的函数定义,并对函数体中的每一条语句调用对应的语义分析器,
|
||||
* 执行类型检查、作用域验证、错误记录等任务。
|
||||
* <p>
|
||||
* 核心职责包括:
|
||||
* <b>设计核心:</b>采用“两遍扫描”方案,彻底解决跨模块全局变量/常量类型推断和引用依赖问题:
|
||||
* <ul>
|
||||
* <li>为每个函数构建局部符号表并注册函数参数为变量;</li>
|
||||
* <li>分发函数体语句至相应的 {@link StatementAnalyzer};</li>
|
||||
* <li>记录未支持语句类型为语义错误;</li>
|
||||
* <li>依赖上下文 {@link Context} 提供模块信息、类型解析、错误收集等服务。</li>
|
||||
* <li><b>第一遍</b>:为所有模块预先构建并注册其全局符号表(globals),保证跨模块引用时可见。</li>
|
||||
* <li><b>第二遍</b>:在全局符号表全部就绪后,依次分析所有模块的函数体,实现局部作用域、类型推断、语义校验等任务。</li>
|
||||
* </ul>
|
||||
* <b>功能职责:</b>
|
||||
* <ul>
|
||||
* <li>遍历所有模块,先建立 globals,再遍历并检查所有函数体语句。</li>
|
||||
* <li>为每个函数体构建完整符号表,并注册参数变量。</li>
|
||||
* <li>分发每条语句到对应 {@link StatementAnalyzer} 进行类型检查和错误校验。</li>
|
||||
* <li>自动检查非 void 函数 return 完备性。</li>
|
||||
* <li>记录所有语义错误,便于前端高亮和诊断。</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param ctx 全局语义分析上下文,提供模块信息、注册表、错误记录等支持
|
||||
* @param ctx 全局语义分析上下文,持有模块信息、符号表、错误收集等资源
|
||||
*/
|
||||
public record FunctionChecker(Context ctx) {
|
||||
|
||||
/**
|
||||
* 构造函数体检查器。
|
||||
*
|
||||
* @param ctx 当前语义分析上下文
|
||||
*/
|
||||
public FunctionChecker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行函数体检查流程。
|
||||
* 主入口:对所有模块的所有函数体进行语义检查(两遍扫描实现)。
|
||||
* <p>
|
||||
* 对所有模块中的所有函数依次进行处理:
|
||||
* <ol>
|
||||
* <li>查找模块对应的 {@link ModuleInfo};</li>
|
||||
* <li>创建函数局部符号表 {@link SymbolTable},并注册所有参数变量;</li>
|
||||
* <li>对函数体中的每一条语句分发到已注册的分析器进行语义分析;</li>
|
||||
* <li>若某条语句无可用分析器,则记录为 {@link SemanticError}。</li>
|
||||
* </ol>
|
||||
* <b>第一遍</b>:为每个模块提前构建全局符号表(包含本模块所有全局变量和常量),
|
||||
* 并注册到 {@link ModuleInfo},确保跨模块引用时所有全局符号都已可用。
|
||||
* <br>
|
||||
* <b>第二遍</b>:遍历所有模块的所有函数,对每个函数体:
|
||||
* <ul>
|
||||
* <li>构建局部作用域,父作用域为对应模块的 globals;</li>
|
||||
* <li>注册参数变量;</li>
|
||||
* <li>依次分发每条语句到对应 {@link StatementAnalyzer},进行类型和语义检查;</li>
|
||||
* <li>自动校验非 void 函数 return 完备性;</li>
|
||||
* <li>将所有发现的问题统一记录到 {@link SemanticError} 列表。</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param mods 所有模块的 AST 根节点集合
|
||||
*/
|
||||
public void check(Iterable<ModuleNode> mods) {
|
||||
List<ModuleNode> moduleList = new ArrayList<>();
|
||||
// ---------- 第1遍:收集所有全局符号表 ----------
|
||||
for (ModuleNode mod : mods) {
|
||||
// 获取当前模块对应的语义信息
|
||||
ModuleInfo mi = ctx.modules().get(mod.name());
|
||||
moduleList.add(mod);
|
||||
|
||||
// 先构建全局符号表
|
||||
// 获取当前模块的元信息
|
||||
ModuleInfo mi = ctx.modules().get(mod.name());
|
||||
// 创建本模块全局作用域(无父作用域)
|
||||
SymbolTable globalScope = new SymbolTable(null);
|
||||
|
||||
// 注册所有全局变量/常量到符号表
|
||||
for (DeclarationNode g : mod.globals()) {
|
||||
var t = ctx.parseType(g.getType());
|
||||
// 检查全局变量是否重复声明
|
||||
if (!globalScope.define(new Symbol(g.getName(), t, SymbolKind.VARIABLE))) {
|
||||
SymbolKind k = g.isConst() ? SymbolKind.CONSTANT : SymbolKind.VARIABLE;
|
||||
String dupType = g.isConst() ? "常量" : "变量";
|
||||
// 检查重复声明
|
||||
if (!globalScope.define(new Symbol(g.getName(), t, k))) {
|
||||
ctx.errors().add(new SemanticError(
|
||||
g,
|
||||
"全局变量重复声明: " + g.getName()
|
||||
dupType + "重复声明: " + g.getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
// 注册到模块信息,供跨模块引用
|
||||
mi.setGlobals(globalScope);
|
||||
}
|
||||
|
||||
// ---------- 第2遍:遍历所有函数,分析函数体 ----------
|
||||
for (ModuleNode mod : moduleList) {
|
||||
ModuleInfo mi = ctx.modules().get(mod.name());
|
||||
SymbolTable globalScope = mi.getGlobals();
|
||||
|
||||
// 遍历模块中所有函数定义
|
||||
for (FunctionNode fn : mod.functions()) {
|
||||
|
||||
// 构建函数局部作用域符号表,父作用域为 globalScope
|
||||
// 构建函数局部作用域,父作用域为 globalScope
|
||||
SymbolTable locals = new SymbolTable(globalScope);
|
||||
|
||||
// 将函数参数注册为局部变量
|
||||
// 注册函数参数为局部变量
|
||||
fn.parameters().forEach(p ->
|
||||
locals.define(new Symbol(
|
||||
p.name(),
|
||||
@ -83,7 +98,7 @@ public record FunctionChecker(Context ctx) {
|
||||
))
|
||||
);
|
||||
|
||||
// 遍历并分析函数体内的每条语句
|
||||
// 分析函数体内每条语句
|
||||
for (var stmt : fn.body()) {
|
||||
var analyzer = ctx.getRegistry().getStatementAnalyzer(stmt);
|
||||
if (analyzer != null) {
|
||||
|
||||
@ -1,33 +1,54 @@
|
||||
package org.jcnc.snow.compiler.semantic.core;
|
||||
|
||||
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
|
||||
import org.jcnc.snow.compiler.semantic.type.FunctionType;
|
||||
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* {@code ModuleInfo} 表示单个模块在语义分析阶段的元信息封装。
|
||||
* <p>
|
||||
* 用于在分析期间管理模块间依赖、函数签名查找等关键任务。
|
||||
* 每个模块对应一个唯一的 {@code ModuleInfo} 实例。
|
||||
* <p>
|
||||
* 包含信息包括:
|
||||
* 用于在分析期间管理模块间依赖、函数签名查找、全局符号表等关键任务。
|
||||
* 每个模块对应一个唯一的 {@code ModuleInfo} 实例,贯穿整个语义分析流程。
|
||||
*
|
||||
* <p><b>包含信息:</b>
|
||||
* <ul>
|
||||
* <li>模块名称(唯一标识);</li>
|
||||
* <li>该模块导入的其他模块名集合;</li>
|
||||
* <li>该模块中定义的所有函数签名 {@code Map<String, FunctionType>}。</li>
|
||||
* <li>模块名称(全局唯一标识);</li>
|
||||
* <li>该模块导入的其他模块名集合(跨模块引用支持);</li>
|
||||
* <li>该模块中定义的所有函数签名 {@code Map<String, FunctionType>};</li>
|
||||
* <li>模块级全局符号表 {@link SymbolTable}(常量 / 全局变量,支持跨模块类型推断)。</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>典型用途:</b>
|
||||
* <ul>
|
||||
* <li>用于函数签名类型查找、重名检测、跨模块引用校验等;</li>
|
||||
* <li>全局符号表为类型检查与后端 IR 常量折叠等模块级分析提供支撑。</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ModuleInfo {
|
||||
|
||||
/** 模块名称,作为全局唯一标识 */
|
||||
/**
|
||||
* 模块名称,作为全局唯一标识
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/** 该模块显式导入的模块名集合(用于跨模块访问符号) */
|
||||
/**
|
||||
* 该模块显式导入的模块名集合(用于跨模块访问符号)
|
||||
*/
|
||||
private final Set<String> imports = new HashSet<>();
|
||||
|
||||
/** 该模块中定义的函数名 → 函数类型列表映射 */
|
||||
private final Map<String, List<FunctionType>> functions = new HashMap<>();
|
||||
/**
|
||||
* 该模块中定义的函数名 → 函数类型映射
|
||||
*/
|
||||
private final Map<String, FunctionType> functions = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 模块级全局符号表(常量 / 全局变量)
|
||||
*/
|
||||
private SymbolTable globals;
|
||||
|
||||
/**
|
||||
* 构造模块信息对象。
|
||||
@ -50,7 +71,7 @@ public class ModuleInfo {
|
||||
/**
|
||||
* 获取该模块导入的模块名称集合。
|
||||
* <p>
|
||||
* 返回集合为内部数据的直接引用,调用方可通过 {@code add/remove} 方法动态维护导入信息。
|
||||
* 返回集合为内部数据的直接引用,调用方可通过 {@code add}/{@code remove} 方法动态维护导入信息。
|
||||
*
|
||||
* @return 可变集合,包含所有导入模块名
|
||||
*/
|
||||
@ -66,57 +87,30 @@ public class ModuleInfo {
|
||||
*
|
||||
* @return 模块内函数定义映射表
|
||||
*/
|
||||
public Map<String, List<FunctionType>> getFunctions() {
|
||||
public Map<String, FunctionType> getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个新的函数类型
|
||||
* 获取模块的全局符号表(包含常量与全局变量)。
|
||||
* <p>
|
||||
* 该符号表由语义分析的 FunctionChecker 阶段构建完成并注入。
|
||||
* 提供跨模块类型检查、常量折叠等能力。
|
||||
*
|
||||
* @param name 函数名
|
||||
* @param ft 函数类型
|
||||
* @return 当前模块的全局符号表
|
||||
*/
|
||||
public void addFunction(String name, FunctionType ft) {
|
||||
if (!functions.containsKey(name)) {
|
||||
functions.put(name, new ArrayList<>());
|
||||
}
|
||||
|
||||
functions.get(name).add(ft);
|
||||
public SymbolTable getGlobals() {
|
||||
return globals;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断函数类型是否存在
|
||||
* 设置模块的全局符号表。
|
||||
* <p>
|
||||
* 仅应由 FunctionChecker 在语义分析全局扫描阶段调用。
|
||||
*
|
||||
* @param name 函数名
|
||||
* @param ft 函数类型
|
||||
* @return 函数类型存在则返回 true,否则 false
|
||||
* @param globals 全局符号表实例
|
||||
*/
|
||||
public boolean existsFunction(String name, FunctionType ft) {
|
||||
if (!functions.containsKey(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return functions.get(name).stream().anyMatch((t) -> t.equals(ft));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检索函数类型
|
||||
* 通过函数名和参数列表唯一确定一个函数类型
|
||||
*
|
||||
* @param name 函数名
|
||||
* @param params 参数列表
|
||||
* @return 唯一确定的函数类型,不存在则返回 null
|
||||
*/
|
||||
public FunctionType retrieveFunction(String name, List<Type> params) {
|
||||
List<FunctionType> fts = functions.get(name);
|
||||
if (fts != null) {
|
||||
for (FunctionType ft : fts) {
|
||||
if (ft.paramTypes().equals(params)) {
|
||||
return ft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
public void setGlobals(SymbolTable globals) {
|
||||
this.globals = globals;
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,17 +78,8 @@ public record SignatureRegistrar(Context ctx) {
|
||||
Type ret = Optional.ofNullable(ctx.parseType(fn.returnType()))
|
||||
.orElse(BuiltinType.VOID);
|
||||
|
||||
FunctionType ft = new FunctionType(params, ret);
|
||||
if (mi.existsFunction(fn.name(), ft)) {
|
||||
ctx.errors().add(new SemanticError(
|
||||
fn,
|
||||
"有歧义的函数: " + fn.name()
|
||||
));
|
||||
}
|
||||
else {
|
||||
// 注册函数签名
|
||||
mi.addFunction(fn.name(), ft);
|
||||
}
|
||||
mi.getFunctions().put(fn.name(), new FunctionType(params, ret));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,31 +3,47 @@ package org.jcnc.snow.compiler.semantic.symbol;
|
||||
/**
|
||||
* {@code SymbolKind} 枚举用于标识符号表中不同类型的命名实体。
|
||||
* <p>
|
||||
* 在语义分析过程中,编译器需要根据符号的种类(Kind)采用不同的处理策略:
|
||||
* 在语义分析过程中,编译器需要根据符号的种类(Kind)采用不同的处理策略:
|
||||
* 例如变量参与类型推导、函数用于调用匹配、模块用于跨作用域引用等。
|
||||
* </p>
|
||||
* <p>
|
||||
* 当前支持的符号种类包括:
|
||||
* 当前支持的符号种类包括:
|
||||
* <ul>
|
||||
* <li>{@link #VARIABLE}: 变量符号(局部变量、全局变量、成员变量等);</li>
|
||||
* <li>{@link #CONSTANT}: 常量符号(只读、不可变的公开常量);</li>
|
||||
* <li>{@link #FUNCTION}: 函数符号(自由函数、方法、构造函数等);</li>
|
||||
* <li>{@link #MODULE}: 模块符号(代表命名空间、库或逻辑模块);</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 可根据需求扩展更多符号种类。
|
||||
* </p>
|
||||
*/
|
||||
public enum SymbolKind {
|
||||
|
||||
/**
|
||||
* 变量符号,表示在作用域中声明的可赋值实体。
|
||||
* <p>
|
||||
* 包括函数参数、局部变量、全局变量、常量等,
|
||||
* 包括函数参数、局部变量、全局变量、成员变量等。
|
||||
* 分析器会基于其类型参与表达式类型校验和赋值检查。
|
||||
* </p>
|
||||
*/
|
||||
VARIABLE,
|
||||
|
||||
/**
|
||||
* 常量符号,表示只读、不可变的公开常量。
|
||||
* <p>
|
||||
* 常量在声明后不可被修改,仅可读取。常用于定义全局、模块级的常量值,
|
||||
* 在类型推断、常量折叠等语义分析过程中单独处理。
|
||||
* </p>
|
||||
*/
|
||||
CONSTANT,
|
||||
|
||||
/**
|
||||
* 函数符号,表示可调用的过程实体。
|
||||
* <p>
|
||||
* 包括普通函数、方法、构造器等。
|
||||
* 用于函数签名注册、函数调用检查及返回值推导。
|
||||
* </p>
|
||||
*/
|
||||
FUNCTION,
|
||||
|
||||
@ -35,6 +51,7 @@ public enum SymbolKind {
|
||||
* 模块符号,表示一个命名空间或模块单元。
|
||||
* <p>
|
||||
* 在跨模块调用、导入语句校验、作用域隔离中发挥作用。
|
||||
* </p>
|
||||
*/
|
||||
MODULE
|
||||
}
|
||||
|
||||
@ -31,7 +31,9 @@ import java.util.List;
|
||||
*/
|
||||
public final class GenerateTask implements Task {
|
||||
|
||||
/** 项目信息元数据 */
|
||||
/**
|
||||
* 项目信息元数据
|
||||
*/
|
||||
private final Project project;
|
||||
|
||||
/**
|
||||
@ -90,6 +92,13 @@ public final class GenerateTask implements Task {
|
||||
System.out.println("[generate] created " + root.relativize(mainSnow));
|
||||
}
|
||||
|
||||
/* ---------- 5. 写入系统库文件 os.snow ---------- */
|
||||
Path osSnow = packageDir.resolve("OS.snow");
|
||||
if (Files.notExists(osSnow)) {
|
||||
Files.writeString(osSnow, SnowExample.getOsModule());
|
||||
System.out.println("[generate] created " + root.relativize(osSnow));
|
||||
}
|
||||
|
||||
System.out.println("[generate] project scaffold is ready.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package org.jcnc.snow.pkg.utils;
|
||||
|
||||
/**
|
||||
* 示例模块模板工具类,提供 main.snow 的标准示例代码字符串。
|
||||
* 示例模块模板工具类,提供标准的示例 Snow 代码片段。
|
||||
* <p>
|
||||
* 用于项目脚手架生成或帮助用户快速上手 .snow 语言。
|
||||
* </p>
|
||||
@ -23,12 +23,11 @@ public final class SnowExample {
|
||||
public static String getMainModule() {
|
||||
return """
|
||||
module: Math
|
||||
import: os
|
||||
function: main
|
||||
params:
|
||||
returns: int
|
||||
returns: void
|
||||
body:
|
||||
Math.factorial(6)
|
||||
return 0
|
||||
os.print(Math.factorial(6))
|
||||
end body
|
||||
end function
|
||||
|
||||
@ -55,4 +54,25 @@ public final class SnowExample {
|
||||
end module
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统库 os.snow 模块的内容字符串。
|
||||
*
|
||||
* @return os.snow 模块的完整代码
|
||||
*/
|
||||
public static String getOsModule() {
|
||||
return """
|
||||
module: os
|
||||
import: os
|
||||
function: print
|
||||
params:
|
||||
declare i1: int
|
||||
returns: void
|
||||
body:
|
||||
syscall("PRINT", i1)
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,36 +10,54 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@code RPushCommand} class implements the {@link Command} interface
|
||||
* and represents the "reference push" instruction ({@code R_PUSH}) in the virtual machine.
|
||||
*
|
||||
* The {@code RPushCommand} class implements the {@link Command} interface and provides
|
||||
* the "reference push" instruction ({@code R_PUSH}) for the virtual machine.
|
||||
* <p>
|
||||
* This instruction pushes a reference-type value onto the operand stack.
|
||||
* The input is parsed from the textual instruction form, which can represent:
|
||||
* <b>Function:</b> Pushes a reference-type value (String literal or array literal) onto the operand stack.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Supported Literals</h2>
|
||||
* <ul>
|
||||
* <li>String literals</li>
|
||||
* <li>Array literals (e.g., {@code [1, 2, 3]}), including nested arrays</li>
|
||||
* <li><b>String Literals:</b> Quoted strings (e.g., {@code "hello\nworld"}) with escape sequence support.</li>
|
||||
* <li><b>Array Literals:</b> Bracketed array forms (e.g., {@code [1, 2, [3, 4]]}), including nested arrays.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* For array literals, a nested list structure is constructed. In this implementation,
|
||||
* array literals are pushed as <b>mutable</b> {@link java.util.ArrayList} structures,
|
||||
* so that subsequent system calls such as {@code ARR_SET} can modify elements in-place.
|
||||
* </p>
|
||||
* <h2>Implementation Details</h2>
|
||||
* <ul>
|
||||
* <li>Array literals are parsed into <b>mutable</b> {@link java.util.ArrayList} objects, to support in-place modification (e.g., by {@code ARR_SET}).</li>
|
||||
* <li>String literals wrapped in quotes are automatically unescaped according to Java string escape rules.</li>
|
||||
* <li>Handles atomic values: numbers (including hex, binary, float, long, short, byte), booleans, and fallback to string.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Example Usage</h2>
|
||||
* <pre>
|
||||
* R_PUSH "hello\nworld" // pushes String "hello\nworld" (with actual newline)
|
||||
* R_PUSH [1, 2, 3] // pushes ArrayList {1, 2, 3}
|
||||
* R_PUSH [1, [2, 3], 4] // pushes nested arrays as mutable lists
|
||||
* </pre>
|
||||
*
|
||||
* @author (your name or org)
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RPushCommand implements Command {
|
||||
|
||||
/**
|
||||
* Executes the R_PUSH command.
|
||||
* Executes the {@code R_PUSH} instruction. Parses the given literal parameter and pushes it onto the operand stack.
|
||||
* <p>
|
||||
* Handles:
|
||||
* <ul>
|
||||
* <li>Array literals (e.g., {@code [1, 2, "a"]}), parsed recursively as mutable ArrayLists</li>
|
||||
* <li>Quoted string literals (e.g., {@code "abc\n"}), parsed with escape sequence support</li>
|
||||
* <li>Unquoted raw strings, numbers, and atoms</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param parts The parts of the instruction, where {@code parts[1..n]} are concatenated as the literal.
|
||||
* @param pc The current program counter.
|
||||
* @param stack The operand stack where the result will be pushed.
|
||||
* @param local The local variable store (unused in this instruction).
|
||||
* @param callStack The call stack (unused in this instruction).
|
||||
* @return The new program counter (typically {@code pc+1}).
|
||||
* @throws IllegalStateException if no literal parameter is provided.
|
||||
* @param parts The instruction split into parts (opcode and arguments)
|
||||
* @param pc The current program counter
|
||||
* @param stack The operand stack to push the value onto
|
||||
* @param local The local variable store (unused)
|
||||
* @param callStack The call stack (unused)
|
||||
* @return The next program counter (pc + 1)
|
||||
* @throws IllegalStateException if the R_PUSH parameter is missing or parsing fails
|
||||
*/
|
||||
@Override
|
||||
public int execute(String[] parts, int pc, OperandStack stack, LocalVariableStore local, CallStack callStack) {
|
||||
@ -54,71 +72,64 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
String literal = sb.toString().trim();
|
||||
|
||||
// Check if this is an array literal
|
||||
// Handle array literal
|
||||
if (literal.startsWith("[") && literal.endsWith("]")) {
|
||||
Object parsed = parseValue(new Cursor(literal));
|
||||
if (!(parsed instanceof List<?> list)) {
|
||||
// Should not happen in theory; safety fallback
|
||||
stack.push(parsed);
|
||||
} else {
|
||||
// Push a deep-mutable copy so ARR_SET can modify elements in-place
|
||||
stack.push(deepMutable(list));
|
||||
}
|
||||
} else {
|
||||
// Regular string, push as-is
|
||||
}
|
||||
// String literal with quotes and escapes
|
||||
else if (literal.length() >= 2 && literal.startsWith("\"") && literal.endsWith("\"")) {
|
||||
String decoded = parseQuoted(new Cursor(literal));
|
||||
stack.push(decoded);
|
||||
}
|
||||
// Raw atom or string
|
||||
else {
|
||||
stack.push(literal);
|
||||
}
|
||||
return pc + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple string cursor, supporting index increment and character reading, for use by the parser.
|
||||
* Utility class for string parsing, used by the array and string literal parsers.
|
||||
*/
|
||||
static class Cursor {
|
||||
final String s;
|
||||
int i;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Cursor} for the given string.
|
||||
*
|
||||
* @param s The string to parse.
|
||||
* Constructs a cursor over the provided string.
|
||||
* @param s the input string to parse
|
||||
*/
|
||||
Cursor(String s) {
|
||||
this.s = s;
|
||||
this.i = 0;
|
||||
}
|
||||
Cursor(String s) { this.s = s; this.i = 0; }
|
||||
|
||||
/**
|
||||
* Advances the cursor by one character.
|
||||
*/
|
||||
void skip() {
|
||||
i++;
|
||||
}
|
||||
void skip() { i++; }
|
||||
|
||||
/**
|
||||
* @return {@code true} if the cursor has reached the end of the string.
|
||||
* Returns true if the cursor has reached the end of the string.
|
||||
* @return true if end of string
|
||||
*/
|
||||
boolean end() {
|
||||
return i >= s.length();
|
||||
}
|
||||
boolean end() { return i >= s.length(); }
|
||||
|
||||
/**
|
||||
* Gets the character at the current cursor position.
|
||||
*
|
||||
* @return current character
|
||||
* @throws StringIndexOutOfBoundsException if at end of string
|
||||
* Returns the current character at the cursor position.
|
||||
* @return the current character
|
||||
*/
|
||||
char ch() {
|
||||
return s.charAt(i);
|
||||
}
|
||||
char ch() { return s.charAt(i); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a value from the input string at the current cursor position.
|
||||
* This can be an array literal, a quoted string, or a simple atom (number, word).
|
||||
* Parses a value from the current cursor position.
|
||||
* Supports arrays, quoted strings, or atoms.
|
||||
*
|
||||
* @param c The cursor for parsing.
|
||||
* @return The parsed value (could be List, String, Number).
|
||||
* @param c the parsing cursor
|
||||
* @return the parsed object (List, String, Number, Boolean, or String fallback)
|
||||
*/
|
||||
Object parseValue(Cursor c) {
|
||||
skipWs(c);
|
||||
@ -130,9 +141,8 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips whitespace characters in the input string.
|
||||
*
|
||||
* @param c The cursor to advance.
|
||||
* Skips whitespace characters at the cursor.
|
||||
* @param c the parsing cursor
|
||||
*/
|
||||
private static void skipWs(Cursor c) {
|
||||
while (!c.end()) {
|
||||
@ -143,13 +153,13 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an array literal from the input, including nested arrays.
|
||||
* Parses an array literal of the form [elem1, elem2, ...] (may be nested).
|
||||
* Recursively parses elements using {@link #parseValue(Cursor)}.
|
||||
*
|
||||
* @param c The cursor (positioned at '[' at entry).
|
||||
* @return A List representing the parsed array.
|
||||
* @param c the parsing cursor
|
||||
* @return a List of parsed elements
|
||||
*/
|
||||
private Object parseArray(Cursor c) {
|
||||
// assumes current char is '['
|
||||
c.skip(); // skip '['
|
||||
List<Object> out = new ArrayList<>();
|
||||
skipWs(c);
|
||||
@ -170,13 +180,12 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a quoted string literal, handling escape characters.
|
||||
* Parses a quoted string, handling standard Java escape sequences (e.g. \n, \t, uXXXX).
|
||||
*
|
||||
* @param c The cursor (positioned at '"' at entry).
|
||||
* @return The parsed string value.
|
||||
* @param c the parsing cursor
|
||||
* @return the decoded string
|
||||
*/
|
||||
private static String parseQuoted(Cursor c) {
|
||||
// assumes current char is '"'
|
||||
c.skip(); // skip opening quote
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (!c.end()) {
|
||||
@ -190,8 +199,25 @@ public class RPushCommand implements Command {
|
||||
case 'n' -> sb.append('\n');
|
||||
case 'r' -> sb.append('\r');
|
||||
case 't' -> sb.append('\t');
|
||||
case 'f' -> sb.append('\f');
|
||||
case 'b' -> sb.append('\b');
|
||||
case '\"' -> sb.append('\"');
|
||||
case '\'' -> sb.append('\'');
|
||||
case '\\' -> sb.append('\\');
|
||||
case 'u' -> { // Unicode escape: uXXXX
|
||||
StringBuilder uni = new StringBuilder();
|
||||
for (int k = 0; k < 4 && !c.end(); ++k) {
|
||||
uni.append(c.ch());
|
||||
c.skip();
|
||||
}
|
||||
try {
|
||||
int code = Integer.parseInt(uni.toString(), 16);
|
||||
sb.append((char) code);
|
||||
} catch (Exception e) {
|
||||
// Invalid unicode, append as is
|
||||
sb.append("\\u").append(uni);
|
||||
}
|
||||
}
|
||||
default -> sb.append(esc);
|
||||
}
|
||||
} else if (ch == '\"') {
|
||||
@ -204,10 +230,10 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an atom (number, hexadecimal, binary, or plain string token).
|
||||
* Parses an atomic value (number, boolean, or fallback string) from the cursor.
|
||||
*
|
||||
* @param c The cursor.
|
||||
* @return An Integer, Double, or String, depending on the content.
|
||||
* @param c the parsing cursor
|
||||
* @return the parsed object (Integer, Double, Float, Long, Boolean, or String)
|
||||
*/
|
||||
private static Object parseAtom(Cursor c) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@ -218,7 +244,7 @@ public class RPushCommand implements Command {
|
||||
c.skip();
|
||||
}
|
||||
String token = sb.toString();
|
||||
// try number
|
||||
// Try number parsing with various notations and types
|
||||
try {
|
||||
if (token.startsWith("0x") || token.startsWith("0X")) {
|
||||
return Integer.parseInt(token.substring(2), 16);
|
||||
@ -226,6 +252,20 @@ public class RPushCommand implements Command {
|
||||
if (token.startsWith("0b") || token.startsWith("0B")) {
|
||||
return Integer.parseInt(token.substring(2), 2);
|
||||
}
|
||||
if (token.endsWith("f")) {
|
||||
return Float.parseFloat(token.substring(0, token.length() - 1));
|
||||
}
|
||||
if (token.endsWith("L")) {
|
||||
return Long.parseLong(token.substring(0, token.length() - 1));
|
||||
}
|
||||
if (token.endsWith("s")) {
|
||||
return Short.parseShort(token.substring(0, token.length() - 1));
|
||||
}
|
||||
if (token.endsWith("b")) {
|
||||
return Byte.parseByte(token.substring(0, token.length() - 1));
|
||||
}
|
||||
if (token.equals("1")) return true;
|
||||
if (token.equals("0")) return false;
|
||||
if (token.contains(".")) {
|
||||
return Double.parseDouble(token);
|
||||
}
|
||||
@ -236,13 +276,11 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- helpers for immutability/mutability ----------------------
|
||||
|
||||
/**
|
||||
* Recursively creates an unmodifiable copy of a list, with all nested lists also unmodifiable.
|
||||
* Creates a deeply unmodifiable version of the provided list (and its nested lists).
|
||||
*
|
||||
* @param l The list to make unmodifiable.
|
||||
* @return An unmodifiable deep copy of the list.
|
||||
* @param l the original list
|
||||
* @return an unmodifiable view of the list and all nested lists
|
||||
*/
|
||||
List<?> deepUnmodifiable(List<?> l) {
|
||||
List<Object> out = new ArrayList<>(l.size());
|
||||
@ -251,10 +289,10 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@link #deepUnmodifiable(List)}. Recursively processes each element.
|
||||
* Helper for {@link #deepUnmodifiable(List)}; handles nested lists recursively.
|
||||
*
|
||||
* @param v The object to process.
|
||||
* @return Unmodifiable list if input is a list, otherwise the value itself.
|
||||
* @param v the object to process
|
||||
* @return an unmodifiable list if input is a list; otherwise, the object itself
|
||||
*/
|
||||
Object deepUnmodifiableObject(Object v) {
|
||||
if (v instanceof List<?> l) {
|
||||
@ -264,11 +302,10 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep mutable copy of a nested List structure, preserving element values.
|
||||
* Nested lists are turned into {@link java.util.ArrayList} so they can be modified by ARR_SET.
|
||||
* Creates a deeply mutable version of the provided list (and its nested lists).
|
||||
*
|
||||
* @param l The source list.
|
||||
* @return Deep mutable copy of the list.
|
||||
* @param l the original list
|
||||
* @return a new mutable list (ArrayList), with all nested lists mutable
|
||||
*/
|
||||
private static java.util.List<?> deepMutable(java.util.List<?> l) {
|
||||
java.util.List<Object> out = new java.util.ArrayList<>(l.size());
|
||||
@ -277,10 +314,10 @@ public class RPushCommand implements Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@link #deepMutable(List)}. Recursively processes each element.
|
||||
* Helper for {@link #deepMutable(List)}; handles nested lists recursively.
|
||||
*
|
||||
* @param v The object to process.
|
||||
* @return Mutable list if input is a list, otherwise the value itself.
|
||||
* @param v the object to process
|
||||
* @return a mutable list if input is a list; otherwise, the object itself
|
||||
*/
|
||||
private static Object deepMutableObject(Object v) {
|
||||
if (v instanceof java.util.List<?> l) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user