Compare commits
7 Commits
main
...
feature/fu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d065bf3cb | ||
|
|
538005b7dc | ||
|
|
7dca18b6f3 | ||
|
|
50cf0abf80 | ||
|
|
6ea8f88b0a | ||
|
|
6b56e65bce | ||
|
|
92bd94a563 |
2
.env
2
.env
@ -1,2 +0,0 @@
|
|||||||
# Auto-generated by build\tools\generate-dotenv.ps1
|
|
||||||
SNOW_VERSION=0.8.0
|
|
||||||
@ -69,7 +69,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: 软件版本/分支
|
label: 软件版本/分支
|
||||||
options:
|
options:
|
||||||
- v0.8.0
|
- v0.6.0
|
||||||
- main
|
- main
|
||||||
- dev
|
- dev
|
||||||
- 其他
|
- 其他
|
||||||
|
|||||||
@ -7,12 +7,4 @@
|
|||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</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>
|
</component>
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Demo22" type="Application" factoryName="Application" folderName="Demo">
|
<configuration default="false" name="Demo22" type="Application" factoryName="Application" folderName="Demo">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
|
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
|
||||||
<module name="Snow" />
|
<module name="Snow" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo22 -o target/Demo22" />
|
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo22 -o target/Demo22 --debug" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
<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>
|
|
||||||
7
.run/Run.run.xml
Normal file
7
.run/Run.run.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<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>
|
||||||
@ -1,6 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="build-project2tar.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build-project2tar.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
|
|
||||||
<envs />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
@ -1,9 +1,9 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<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">
|
<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 />
|
<envs />
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
<configuration default="false" name="build-release-all.ps1" type="PowerShellRunType" factoryName="PowerShell" scriptUrl="$PROJECT_DIR$/build/build-release-all.ps1" executablePath="C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe">
|
<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 />
|
<envs />
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
@ -1,6 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
<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,11 +13,6 @@
|
|||||||
<toRun name="Demo19" type="Application" />
|
<toRun name="Demo19" type="Application" />
|
||||||
<toRun name="Demo2" type="Application" />
|
<toRun name="Demo2" type="Application" />
|
||||||
<toRun name="Demo20" 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="Demo3" type="Application" />
|
||||||
<toRun name="Demo4" type="Application" />
|
<toRun name="Demo4" type="Application" />
|
||||||
<toRun name="Demo6" type="Application" />
|
<toRun name="Demo6" type="Application" />
|
||||||
|
|||||||
57
Dockerfile
57
Dockerfile
@ -1,57 +0,0 @@
|
|||||||
# 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">
|
<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="">
|
<img src="https://img.shields.io/badge/%20license-Apache--2.0%20-blue" alt="">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://gitee.com/jcnc-org/snow/tree/v0.8.0/">
|
<a href="https://gitee.com/jcnc-org/snow/tree/v0.6.0/">
|
||||||
<img src="https://img.shields.io/badge/version-v0.8.0-blue" alt="">
|
<img src="https://img.shields.io/badge/version-v0.6.0-blue" alt="">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
# 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
|
|
||||||
}
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
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
|
|
||||||
47
build/build_project2tar.ps1
Normal file
47
build/build_project2tar.ps1
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# 设定 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
|
||||||
|
}
|
||||||
@ -1,136 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
# 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
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
# 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
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
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,15 +1,46 @@
|
|||||||
module: Main
|
module: Main
|
||||||
import: ModuleA,os
|
import: os
|
||||||
function: main
|
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
|
returns: void
|
||||||
body:
|
body:
|
||||||
declare sum: int = ModuleA.a+2
|
os.syscall("PRINTLN",i1)
|
||||||
os.print(sum+1)
|
|
||||||
end body
|
end body
|
||||||
end function
|
end function
|
||||||
end module
|
end module
|
||||||
|
|
||||||
module: ModuleA
|
|
||||||
globals:
|
|
||||||
declare const a: int =10
|
|
||||||
end module
|
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
module: os
|
|
||||||
import: os
|
|
||||||
function: print
|
|
||||||
params:
|
|
||||||
declare i1: int
|
|
||||||
returns: void
|
|
||||||
body:
|
|
||||||
syscall("PRINT",i1)
|
|
||||||
end body
|
|
||||||
end function
|
|
||||||
end module
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
module: os
|
|
||||||
import: os
|
|
||||||
function: print
|
|
||||||
params:
|
|
||||||
declare i1: int
|
|
||||||
returns: void
|
|
||||||
body:
|
|
||||||
syscall("PRINT",i1)
|
|
||||||
end body
|
|
||||||
end function
|
|
||||||
end module
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
module: os
|
|
||||||
import: os
|
|
||||||
function: print
|
|
||||||
params:
|
|
||||||
declare i1: int
|
|
||||||
returns: void
|
|
||||||
body:
|
|
||||||
syscall("PRINT",i1)
|
|
||||||
end body
|
|
||||||
end function
|
|
||||||
end module
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
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>
|
<groupId>org.jcnc.snow</groupId>
|
||||||
<artifactId>Snow</artifactId>
|
<artifactId>Snow</artifactId>
|
||||||
<version>0.8.0</version>
|
<version>0.6.0</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
@ -70,7 +70,11 @@
|
|||||||
</build>
|
</build>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
<!-- 原生镜像构建: Linux 平台(使用 Docker builder 中的 musl 工具链) -->
|
<!--
|
||||||
|
原生镜像构建: Linux 平台
|
||||||
|
- 使用 GraalVM 的 native-image 工具,生成静态链接的可执行文件
|
||||||
|
- 依赖 musl libc,需提前安装并配置 musl-gcc 工具链
|
||||||
|
-->
|
||||||
<profile>
|
<profile>
|
||||||
<id>native-linux</id>
|
<id>native-linux</id>
|
||||||
<activation>
|
<activation>
|
||||||
@ -78,39 +82,25 @@
|
|||||||
<family>unix</family>
|
<family>unix</family>
|
||||||
</os>
|
</os>
|
||||||
</activation>
|
</activation>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.graalvm.buildtools</groupId>
|
<groupId>org.graalvm.buildtools</groupId>
|
||||||
<artifactId>native-maven-plugin</artifactId>
|
<artifactId>native-maven-plugin</artifactId>
|
||||||
<version>${native.maven.plugin.version}</version>
|
<version>${native.maven.plugin.version}</version>
|
||||||
|
<!-- 启用插件扩展,允许在 build 生命周期中无须额外配置 -->
|
||||||
<extensions>true</extensions>
|
<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>
|
<executions>
|
||||||
|
<!-- 打包阶段生成原生可执行文件 -->
|
||||||
<execution>
|
<execution>
|
||||||
<id>build-native</id>
|
<id>build-native</id>
|
||||||
<goals>
|
<goals>
|
||||||
|
<!-- compile-no-fork 在当前 JVM 进程中执行 native-image -->
|
||||||
<goal>compile-no-fork</goal>
|
<goal>compile-no-fork</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
</execution>
|
</execution>
|
||||||
|
<!-- 测试阶段运行原生镜像的测试 -->
|
||||||
<execution>
|
<execution>
|
||||||
<id>test-native</id>
|
<id>test-native</id>
|
||||||
<goals>
|
<goals>
|
||||||
@ -119,6 +109,24 @@
|
|||||||
<phase>test</phase>
|
<phase>test</phase>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</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>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
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,7 +72,8 @@ public class CallGenerator implements InstructionGenerator<CallInstruction> {
|
|||||||
String fn = ins.getFunctionName();
|
String fn = ins.getFunctionName();
|
||||||
|
|
||||||
// 特殊处理 syscall 调用
|
// 特殊处理 syscall 调用
|
||||||
if ("syscall".equals(fn) || fn.endsWith(".syscall")) {
|
String tname = fn.substring(0, fn.lastIndexOf(':'));
|
||||||
|
if ("syscall".equals(tname) || tname.endsWith(".syscall")) {
|
||||||
generateSyscall(ins, out, slotMap, fn);
|
generateSyscall(ins, out, slotMap, fn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,47 +12,46 @@ import java.util.Map;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>LoadConstGenerator</b>
|
* <b>LoadConstGenerator - Generates VM instructions from IR {@code LoadConstInstruction}</b>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This generator converts an IR-level {@link LoadConstInstruction} into corresponding VM instructions.
|
* This class is responsible for converting IR-level {@link LoadConstInstruction} into corresponding VM instructions.
|
||||||
* If the constant is a {@code String}, it will also be registered in the
|
* If the constant is a {@code String}, it will also be registered in the
|
||||||
* {@link CallGenerator} string constant pool for later use.
|
* {@link CallGenerator} string constant pool to support syscall downgrade scenarios.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Key implementation notes:
|
* Fix: When the constant is an array (List), type information is preserved in R_PUSH payload:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>When the constant is an array (List), type information is preserved in the R_PUSH payload:</li>
|
* <li>Float is output with <code>f</code> suffix (e.g., 0.1f);</li>
|
||||||
* <li>Float values get an <code>f</code> suffix (e.g., 0.1f)</li>
|
* <li>Long is output with <code>L</code> suffix (e.g., 123L);</li>
|
||||||
* <li>Long values get an <code>L</code> suffix (e.g., 123L)</li>
|
* <li>Double/Integer are output in their default format (e.g., 1.0, 42);</li>
|
||||||
* <li>Double and Integer values use their default string format (e.g., 1.0, 42)</li>
|
* <li>Supports recursive serialization of nested arrays.</li>
|
||||||
* <li>Nested arrays are recursively serialized with correct type suffixes.</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
* This prevents type confusion on the VM side (e.g., float being misread as double)
|
* This prevents float values from being misinterpreted as double on the VM side,
|
||||||
* and avoids cast exceptions during store operations.
|
* and avoids Double→Float cast exceptions in later F_STORE operations.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruction> {
|
public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruction> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a constant value for use as a VM instruction payload.
|
* Formats a constant value as a string for use as a VM payload.
|
||||||
* For lists, recursively formats each element with type suffixes where appropriate.
|
* Lists are recursively serialized, and Float/Long types include suffixes to preserve type information.
|
||||||
*
|
*
|
||||||
* @param v The constant value.
|
* @param v The constant value to format.
|
||||||
* @return The formatted string payload for VM code.
|
* @return The formatted string for use in VM code.
|
||||||
*/
|
*/
|
||||||
private static String formatConst(Object v) {
|
private static String formatConst(Object v) {
|
||||||
return formatConst(v, false);
|
return formatConst(v, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively formats constant values (including nested arrays), preserving
|
* Internal helper for recursively formatting constant values (including nested arrays)
|
||||||
* type suffixes and escaping strings. Used internally for array/list handling.
|
* with appropriate type suffixes for array payloads.
|
||||||
*
|
*
|
||||||
* @param v The constant value.
|
* @param v The constant value to format.
|
||||||
* @param insideArray Whether this value is inside an array context (controls type suffixing).
|
* @param insideArray True if currently formatting inside an array context; affects whether type suffixes are applied.
|
||||||
* @return The formatted string for VM code.
|
* @return The formatted string for use in VM code.
|
||||||
*/
|
*/
|
||||||
private static String formatConst(Object v, boolean insideArray) {
|
private static String formatConst(Object v, boolean insideArray) {
|
||||||
if (v instanceof List<?> list) {
|
if (v instanceof List<?> list) {
|
||||||
@ -62,10 +61,10 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
|||||||
.collect(Collectors.joining(", ")) + "]";
|
.collect(Collectors.joining(", ")) + "]";
|
||||||
}
|
}
|
||||||
if (v instanceof String s) {
|
if (v instanceof String s) {
|
||||||
// Escape and wrap the string in double quotes, to avoid line breaks or control chars breaking VM code
|
return s;
|
||||||
return "\"" + escape(s) + "\"";
|
|
||||||
}
|
}
|
||||||
if (v instanceof Float f) {
|
if (v instanceof Float f) {
|
||||||
|
// Always keep .0 for integer values
|
||||||
float fv = f;
|
float fv = f;
|
||||||
String s = (fv == (long) fv) ? String.format("%.1f", fv) : f.toString();
|
String s = (fv == (long) fv) ? String.format("%.1f", fv) : f.toString();
|
||||||
return insideArray ? (s + "f") : s;
|
return insideArray ? (s + "f") : s;
|
||||||
@ -75,6 +74,7 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
|||||||
}
|
}
|
||||||
if (v instanceof Double d) {
|
if (v instanceof Double d) {
|
||||||
double dv = d;
|
double dv = d;
|
||||||
|
// Always keep .0 for integer values
|
||||||
return (dv == (long) dv) ? String.format("%.1f", dv) : Double.toString(dv);
|
return (dv == (long) dv) ? String.format("%.1f", dv) : Double.toString(dv);
|
||||||
}
|
}
|
||||||
if (v instanceof Short s) {
|
if (v instanceof Short s) {
|
||||||
@ -89,54 +89,28 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
|||||||
return String.valueOf(v);
|
return String.valueOf(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the type of IR instruction supported by this generator.
|
||||||
|
*
|
||||||
|
* @return The class object representing {@link LoadConstInstruction}.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Class<LoadConstInstruction> supportedClass() {
|
public Class<LoadConstInstruction> supportedClass() {
|
||||||
return LoadConstInstruction.class;
|
return LoadConstInstruction.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates VM code for a LoadConstInstruction.
|
* Generates the VM instructions for a given {@link LoadConstInstruction}.
|
||||||
* Produces PUSH and STORE instructions, sets the slot type,
|
* <p>
|
||||||
* and registers string constants if necessary.
|
* 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>
|
||||||
*
|
*
|
||||||
* @param ins The IR instruction to generate.
|
* @param ins The {@link LoadConstInstruction} to generate code for.
|
||||||
* @param out The output program builder.
|
* @param out The {@link VMProgramBuilder} used to collect the generated instructions.
|
||||||
* @param slotMap The mapping from IR virtual register to physical slot.
|
* @param slotMap A mapping from {@link IRVirtualRegister} to physical slot indices.
|
||||||
* @param currentFn The current function name.
|
* @param currentFn The name of the current function.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void generate(LoadConstInstruction ins,
|
public void generate(LoadConstInstruction ins,
|
||||||
@ -144,19 +118,19 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
|||||||
Map<IRVirtualRegister, Integer> slotMap,
|
Map<IRVirtualRegister, Integer> slotMap,
|
||||||
String currentFn) {
|
String currentFn) {
|
||||||
|
|
||||||
// 1. Retrieve the constant value from the instruction
|
// 1. Get the constant value
|
||||||
IRConstant constant = (IRConstant) ins.operands().getFirst();
|
IRConstant constant = (IRConstant) ins.operands().getFirst();
|
||||||
Object value = constant.value();
|
Object value = constant.value();
|
||||||
|
|
||||||
// 2. Format and emit the PUSH instruction (arrays will use type-aware formatting)
|
// 2. Generate PUSH instruction (array constants use type-aware formatting)
|
||||||
String payload = formatConst(value);
|
String payload = formatConst(value);
|
||||||
out.emit(OpHelper.pushOpcodeFor(value) + " " + payload);
|
out.emit(OpHelper.pushOpcodeFor(value) + " " + payload);
|
||||||
|
|
||||||
// 3. Emit STORE to the destination slot
|
// 3. STORE the result to the destination slot
|
||||||
int slot = slotMap.get(ins.dest());
|
int slot = slotMap.get(ins.dest());
|
||||||
out.emit(OpHelper.storeOpcodeFor(value) + " " + slot);
|
out.emit(OpHelper.storeOpcodeFor(value) + " " + slot);
|
||||||
|
|
||||||
// 4. Mark the slot's data type for later use (type inference, instruction selection, etc.)
|
// 4. Mark the slot's data type for later inference and instruction selection
|
||||||
char prefix = switch (value) {
|
char prefix = switch (value) {
|
||||||
case Integer _ -> 'I'; // Integer
|
case Integer _ -> 'I'; // Integer
|
||||||
case Long _ -> 'L'; // Long
|
case Long _ -> 'L'; // Long
|
||||||
@ -164,15 +138,15 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
|
|||||||
case Byte _ -> 'B'; // Byte
|
case Byte _ -> 'B'; // Byte
|
||||||
case Double _ -> 'D'; // Double
|
case Double _ -> 'D'; // Double
|
||||||
case Float _ -> 'F'; // Float
|
case Float _ -> 'F'; // Float
|
||||||
case Boolean _ -> 'I'; // Booleans are treated as integers (1/0)
|
case Boolean _ -> 'I'; // Boolean handled as Integer (typically lowered to 1/0)
|
||||||
case String _ -> 'R'; // Reference type for strings
|
case String _ -> 'R'; // String constant
|
||||||
case java.util.List<?> _ -> 'R'; // Reference type for arrays/lists
|
case java.util.List<?> _ -> 'R'; // Reference type (arrays, etc.)
|
||||||
case null, default -> throw new IllegalStateException("Unknown constant type: "
|
case null, default -> throw new IllegalStateException("Unknown constant type: "
|
||||||
+ (value != null ? value.getClass() : null));
|
+ (value != null ? value.getClass() : null));
|
||||||
};
|
};
|
||||||
out.setSlotType(slot, prefix);
|
out.setSlotType(slot, prefix);
|
||||||
|
|
||||||
// 5. Register the string constant for the string constant pool if needed
|
// 5. If the constant is a string, register it for the CallGenerator string pool
|
||||||
if (value instanceof String s) {
|
if (value instanceof String s) {
|
||||||
CallGenerator.registerStringConst(ins.dest().id(), s);
|
CallGenerator.registerStringConst(ins.dest().id(), s);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package org.jcnc.snow.compiler.ir.builder;
|
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.IROpCode;
|
||||||
import org.jcnc.snow.compiler.ir.core.IRValue;
|
import org.jcnc.snow.compiler.ir.core.IRValue;
|
||||||
import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
|
import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
|
||||||
@ -15,6 +14,9 @@ import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
|
import static org.jcnc.snow.compiler.ir.utils.ExpressionUtils.looksLikeFloat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code ExpressionBuilder} 表达式 → IR 构建器。
|
* {@code ExpressionBuilder} 表达式 → IR 构建器。
|
||||||
@ -49,8 +51,6 @@ public record ExpressionBuilder(IRContext ctx) {
|
|||||||
if (reg == null) throw new IllegalStateException("未定义标识符: " + id.name());
|
if (reg == null) throw new IllegalStateException("未定义标识符: " + id.name());
|
||||||
yield reg;
|
yield reg;
|
||||||
}
|
}
|
||||||
// 模块常量 / 全局变量,如 ModuleA.a
|
|
||||||
case MemberExpressionNode mem -> buildMember(mem);
|
|
||||||
// 二元表达式(如 a+b, x==y)
|
// 二元表达式(如 a+b, x==y)
|
||||||
case BinaryExpressionNode bin -> buildBinary(bin);
|
case BinaryExpressionNode bin -> buildBinary(bin);
|
||||||
// 函数/方法调用表达式
|
// 函数/方法调用表达式
|
||||||
@ -65,40 +65,6 @@ 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ───────────────── 写入指定寄存器 ───────────────── */
|
/* ───────────────── 写入指定寄存器 ───────────────── */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -414,24 +380,73 @@ public record ExpressionBuilder(IRContext ctx) {
|
|||||||
// 1. 递归生成所有参数的寄存器
|
// 1. 递归生成所有参数的寄存器
|
||||||
List<IRVirtualRegister> argv = call.arguments().stream().map(this::build).toList();
|
List<IRVirtualRegister> argv = call.arguments().stream().map(this::build).toList();
|
||||||
|
|
||||||
// 2. 规范化被调用方法名(区分成员方法与普通函数)
|
// 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. 规范化被调用方法名(区分成员方法与普通函数)
|
||||||
String callee = switch (call.callee()) {
|
String callee = switch (call.callee()) {
|
||||||
// 成员方法调用,如 obj.method()
|
// 成员方法调用,如 obj.method()
|
||||||
case MemberExpressionNode m when m.object() instanceof IdentifierNode id -> id.name() + "." + m.member();
|
case MemberExpressionNode m when m.object() instanceof IdentifierNode id -> {
|
||||||
|
String qualifiedName = id.name() + "." + m.member();
|
||||||
|
if (sj.length() > 0) {
|
||||||
|
qualifiedName = qualifiedName + ":" + sj;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield qualifiedName;
|
||||||
|
}
|
||||||
// 普通函数调用,或处理命名空间前缀(如当前方法名为 namespace.func)
|
// 普通函数调用,或处理命名空间前缀(如当前方法名为 namespace.func)
|
||||||
case IdentifierNode id -> {
|
case IdentifierNode id -> {
|
||||||
String current = ctx.getFunction().name();
|
String current = ctx.getFunction().name();
|
||||||
int dot = current.lastIndexOf('.');
|
int dot = current.lastIndexOf('.');
|
||||||
if (dot > 0)
|
|
||||||
yield current.substring(0, dot) + "." + id.name(); // 同命名空间内调用
|
String qualifiedName = dot > 0
|
||||||
yield id.name(); // 全局函数调用
|
? current.substring(0, dot) + "." + id.name()
|
||||||
|
: id.name();
|
||||||
|
if (sj.length() > 0) {
|
||||||
|
qualifiedName = qualifiedName + ":" + sj;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield qualifiedName; // 全局函数调用
|
||||||
}
|
}
|
||||||
// 其它类型不支持
|
// 其它类型不支持
|
||||||
default -> throw new IllegalStateException(
|
default -> throw new IllegalStateException(
|
||||||
"不支持的调用目标: " + call.callee().getClass().getSimpleName());
|
"不支持的调用目标: " + call.callee().getClass().getSimpleName());
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. 分配用于存放返回值的新寄存器,并生成 Call 指令
|
// 4. 分配用于存放返回值的新寄存器,并生成 Call 指令
|
||||||
IRVirtualRegister dest = ctx.newRegister();
|
IRVirtualRegister dest = ctx.newRegister();
|
||||||
ctx.addInstruction(new CallInstruction(dest, callee, new ArrayList<>(argv)));
|
ctx.addInstruction(new CallInstruction(dest, callee, new ArrayList<>(argv)));
|
||||||
return dest;
|
return dest;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package org.jcnc.snow.compiler.ir.builder;
|
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.common.GlobalFunctionTable;
|
||||||
import org.jcnc.snow.compiler.ir.core.IRFunction;
|
import org.jcnc.snow.compiler.ir.core.IRFunction;
|
||||||
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
|
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
|
||||||
@ -14,13 +13,6 @@ import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
|
|||||||
* <p>
|
* <p>
|
||||||
* 负责将语法树中的 FunctionNode 节点转化为可执行的 IRFunction,
|
* 负责将语法树中的 FunctionNode 节点转化为可执行的 IRFunction,
|
||||||
* 包含参数声明、返回类型推断、函数体语句转换等步骤。
|
* 包含参数声明、返回类型推断、函数体语句转换等步骤。
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>支持自动导入全局/跨模块常量,使跨模块常量引用(如 ModuleA.a)在 IR 阶段可用。</li>
|
|
||||||
* <li>将函数形参声明为虚拟寄存器,并注册到作用域,便于后续指令生成。</li>
|
|
||||||
* <li>根据返回类型设置表达式默认字面量类型,保证 IR 层类型一致性。</li>
|
|
||||||
* <li>遍历并转换函数体语句为 IR 指令。</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
*/
|
||||||
public class FunctionBuilder {
|
public class FunctionBuilder {
|
||||||
|
|
||||||
@ -29,15 +21,11 @@ public class FunctionBuilder {
|
|||||||
* <p>
|
* <p>
|
||||||
* 构建过程包括:
|
* 构建过程包括:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>在全局函数表注册函数签名,便于后续模块/语义分析阶段查询。</li>
|
* <li>初始化 IRFunction 实例和上下文</li>
|
||||||
* <li>初始化 IRFunction 实例和 IRContext 上下文对象(包含作用域与寄存器信息)。</li>
|
* <li>根据函数返回类型,设置默认类型后缀,便于表达式推断</li>
|
||||||
* <li>自动导入全局常量(包括跨模块 const 变量)到当前作用域,
|
* <li>声明参数到作用域,并为每个参数分配虚拟寄存器</li>
|
||||||
* 使成员访问如 ModuleA.a 可直接折叠为常量。</li>
|
* <li>遍历并转换函数体内的每条语句为 IR 指令</li>
|
||||||
* <li>根据函数返回类型,设置表达式推断的默认字面量类型后缀
|
* <li>函数构建完成后,清理默认类型后缀,防止影响其他函数</li>
|
||||||
* (如 double→d, float→f),避免类型不一致。</li>
|
|
||||||
* <li>遍历声明形参,每个参数分配虚拟寄存器,并注册到作用域。</li>
|
|
||||||
* <li>依次转换函数体中的每条语句为 IR 指令。</li>
|
|
||||||
* <li>函数体转换完成后,清理默认类型后缀,防止影响后续函数构建。</li>
|
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
* @param functionNode 表示函数定义的语法树节点
|
* @param functionNode 表示函数定义的语法树节点
|
||||||
@ -45,56 +33,44 @@ public class FunctionBuilder {
|
|||||||
*/
|
*/
|
||||||
public IRFunction build(FunctionNode functionNode) {
|
public IRFunction build(FunctionNode functionNode) {
|
||||||
|
|
||||||
// 1. 在全局函数表注册函数名与返回类型
|
// 在全局函数表中注册函数信息
|
||||||
// 方便其他阶段/模块调用、类型检查。
|
|
||||||
GlobalFunctionTable.register(functionNode.name(), functionNode.returnType());
|
GlobalFunctionTable.register(functionNode.name(), functionNode.returnType());
|
||||||
|
|
||||||
// 2. 初始化 IRFunction 实例与上下文对象
|
|
||||||
// IRFunction: 表示该函数的中间代码容器
|
// 0) 基本初始化: 创建 IRFunction 实例与对应上下文
|
||||||
// IRContext: 负责作用域、寄存器分配等编译上下文管理
|
|
||||||
IRFunction irFunction = new IRFunction(functionNode.name());
|
IRFunction irFunction = new IRFunction(functionNode.name());
|
||||||
IRContext irContext = new IRContext(irFunction);
|
IRContext irContext = new IRContext(irFunction);
|
||||||
|
|
||||||
// 3. 自动导入所有全局/跨模块常量到当前作用域
|
// 1) 把函数返回类型注入为默认类型后缀(供表达式类型推断)
|
||||||
// 支持如 ModuleA.a 这样的常量访问/折叠(参见 ExpressionBuilder)
|
|
||||||
GlobalConstTable.all().forEach((k, v) ->
|
|
||||||
irContext.getScope().importExternalConst(k, v));
|
|
||||||
|
|
||||||
// 4. 根据函数返回类型设置默认类型后缀
|
|
||||||
// 例如返回类型为 double 时, 字面量表达式自动用 d 后缀。
|
|
||||||
char _returnSuffix = switch (functionNode.returnType().toLowerCase()) {
|
char _returnSuffix = switch (functionNode.returnType().toLowerCase()) {
|
||||||
case "double" -> 'd';
|
case "double" -> 'd';
|
||||||
case "float" -> 'f';
|
case "float" -> 'f';
|
||||||
case "long" -> 'l';
|
case "long" -> 'l';
|
||||||
case "short" -> 's';
|
case "short" -> 's';
|
||||||
case "byte" -> 'b';
|
case "byte" -> 'b';
|
||||||
default -> '\0'; // 其它类型不设默认后缀
|
default -> '\0';
|
||||||
};
|
};
|
||||||
ExpressionUtils.setDefaultSuffix(_returnSuffix);
|
ExpressionUtils.setDefaultSuffix(_returnSuffix);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 5. 遍历函数参数列表
|
// 2) 声明形参: 为每个参数分配虚拟寄存器并声明到作用域
|
||||||
// - 为每个参数分配一个新的虚拟寄存器
|
|
||||||
// - 注册参数名、类型、寄存器到当前作用域
|
|
||||||
// - 添加参数寄存器到 IRFunction(用于后续调用与指令生成)
|
|
||||||
for (ParameterNode p : functionNode.parameters()) {
|
for (ParameterNode p : functionNode.parameters()) {
|
||||||
IRVirtualRegister reg = irFunction.newRegister();
|
IRVirtualRegister reg = irFunction.newRegister(); // 新寄存器
|
||||||
irContext.getScope().declare(p.name(), p.type(), reg);
|
irContext.getScope().declare(p.name(), p.type(), reg); // 变量名→寄存器绑定
|
||||||
irFunction.addParameter(reg);
|
irFunction.addParameter(reg, p.type()); // 添加到函数参数列表
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 遍历函数体语句节点,转换为 IR 指令
|
// 3) 生成函数体 IR: 遍历每条语句,逐一转化
|
||||||
// StatementBuilder 负责将每条语句递归转换为 IR
|
|
||||||
StatementBuilder stmtBuilder = new StatementBuilder(irContext);
|
StatementBuilder stmtBuilder = new StatementBuilder(irContext);
|
||||||
for (StatementNode stmt : functionNode.body()) {
|
for (StatementNode stmt : functionNode.body()) {
|
||||||
stmtBuilder.build(stmt);
|
stmtBuilder.build(stmt);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// 7. 清理默认类型后缀,防止影响后续其他函数的类型推断
|
// 4) 清除默认后缀,避免影响后续函数的推断
|
||||||
ExpressionUtils.clearDefaultSuffix();
|
ExpressionUtils.clearDefaultSuffix();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. 返回构建完成的 IRFunction
|
// 返回构建好的 IRFunction
|
||||||
return irFunction;
|
return irFunction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,25 +15,30 @@ import java.util.Map;
|
|||||||
* <li>支持变量与虚拟寄存器的重新绑定与查找</li>
|
* <li>支持变量与虚拟寄存器的重新绑定与查找</li>
|
||||||
* <li>支持变量的类型信息记录与查询</li>
|
* <li>支持变量的类型信息记录与查询</li>
|
||||||
* <li>支持变量的编译期常量值记录与查询(便于常量折叠等优化)</li>
|
* <li>支持变量的编译期常量值记录与查询(便于常量折叠等优化)</li>
|
||||||
* <li>支持跨模块全局常量(如 ModuleA.a)查找</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
final class IRBuilderScope {
|
final class IRBuilderScope {
|
||||||
|
|
||||||
/** 变量名到虚拟寄存器的映射表(本地变量) */
|
/**
|
||||||
|
* 变量名到虚拟寄存器的映射表。
|
||||||
|
* 用于跟踪所有声明和分配的变量。
|
||||||
|
*/
|
||||||
private final Map<String, IRVirtualRegister> vars = new HashMap<>();
|
private final Map<String, IRVirtualRegister> vars = new HashMap<>();
|
||||||
/** 变量名到类型字符串的映射表 */
|
|
||||||
private final Map<String, String> varTypes = new HashMap<>();
|
|
||||||
/** 变量名到编译期常量值的映射表(本作用域) */
|
|
||||||
private final Map<String, Object> varConstValues = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 额外:存放跨模块导入的全局常量
|
* 变量名到类型字符串的映射表。
|
||||||
* key 形如 "ModuleA.a" value 为其常量值
|
* 用于类型分析与推断。
|
||||||
|
*/
|
||||||
|
private final Map<String, String> varTypes = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* 变量名到编译期常量值的映射表。
|
||||||
|
* 用于常量折叠优化(如 int、string、数组等常量)。
|
||||||
|
*/
|
||||||
|
private final Map<String, Object> varConstValues = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* 当前作用域所绑定的 IRFunction 实例。
|
||||||
|
* 用于申请新的虚拟寄存器。
|
||||||
*/
|
*/
|
||||||
private final Map<String, Object> externalConsts = new HashMap<>();
|
|
||||||
|
|
||||||
/** 当前作用域所绑定的 IRFunction 实例 */
|
|
||||||
private IRFunction fn;
|
private IRFunction fn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,7 +118,7 @@ final class IRBuilderScope {
|
|||||||
// ---------------- 编译期常量相关接口 ----------------
|
// ---------------- 编译期常量相关接口 ----------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置变量的编译期常量值(本地变量)。
|
* 设置变量的编译期常量值。
|
||||||
*
|
*
|
||||||
* @param name 变量名称
|
* @param name 变量名称
|
||||||
* @param value 常量值(null 表示清除)
|
* @param value 常量值(null 表示清除)
|
||||||
@ -124,38 +129,21 @@ final class IRBuilderScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取变量的编译期常量值(本地变量或导入的外部常量)。
|
* 获取变量的编译期常量值(如没有则返回 null)。
|
||||||
* <br>
|
|
||||||
* 优先查找本地常量,未命中再查外部(如 "ModuleA.a")。
|
|
||||||
*
|
*
|
||||||
* @param name 变量名称或"模块名.常量名"
|
* @param name 变量名称
|
||||||
* @return 编译期常量值,或 null
|
* @return 编译期常量值,或 null
|
||||||
*/
|
*/
|
||||||
Object getConstValue(String name) {
|
Object getConstValue(String name) {
|
||||||
Object v = varConstValues.get(name);
|
return varConstValues.get(name);
|
||||||
if (v != null) return v;
|
|
||||||
// 支持跨模块常量/全局变量
|
|
||||||
return externalConsts.get(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除变量的编译期常量值绑定(本地)。
|
* 清除变量的编译期常量值绑定。
|
||||||
*
|
*
|
||||||
* @param name 变量名称
|
* @param name 变量名称
|
||||||
*/
|
*/
|
||||||
void clearConstValue(String name) {
|
void clearConstValue(String name) {
|
||||||
varConstValues.remove(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;
|
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.IRFunction;
|
||||||
import org.jcnc.snow.compiler.ir.core.IRProgram;
|
import org.jcnc.snow.compiler.ir.core.IRProgram;
|
||||||
import org.jcnc.snow.compiler.parser.ast.*;
|
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
|
||||||
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
|
import org.jcnc.snow.compiler.parser.ast.ModuleNode;
|
||||||
|
import org.jcnc.snow.compiler.parser.ast.ParameterNode;
|
||||||
import org.jcnc.snow.compiler.parser.ast.base.Node;
|
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.NodeContext;
|
||||||
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
|
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IRProgramBuilder 负责将 AST 顶层节点转换为可执行的 {@link IRProgram}。
|
* IRProgramBuilder 负责将 AST 根节点(如模块、函数、顶层语句)转换为可执行的 IRProgram 实例。
|
||||||
*
|
* <p>
|
||||||
|
* 主要职责:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>预扫描所有模块,将 <code>declare const</code> 常量登记到全局常量表,支持跨模块常量折叠。</li>
|
* <li>遍历 AST 根节点,根据类型分别处理(模块、函数、顶层语句)。</li>
|
||||||
* <li>对模块内的函数加上模块前缀,保证命名唯一,并将本模块全局声明注入到函数体前部。</li>
|
* <li>对模块内的函数添加全限定名,并在函数体前注入全局变量声明。</li>
|
||||||
* <li>将独立顶层语句自动包装为特殊的 "_start" 函数(脚本模式支持)。</li>
|
* <li>将单独的顶层语句封装为特殊的 "_start" 函数。</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public final class IRProgramBuilder {
|
public final class IRProgramBuilder {
|
||||||
@ -33,23 +33,20 @@ public final class IRProgramBuilder {
|
|||||||
* @throws IllegalStateException 遇到不支持的顶层节点类型时抛出
|
* @throws IllegalStateException 遇到不支持的顶层节点类型时抛出
|
||||||
*/
|
*/
|
||||||
public IRProgram buildProgram(List<Node> roots) {
|
public IRProgram buildProgram(List<Node> roots) {
|
||||||
// 预先收集并登记全部模块常量到全局常量表
|
|
||||||
preloadGlobals(roots);
|
|
||||||
|
|
||||||
IRProgram irProgram = new IRProgram();
|
IRProgram irProgram = new IRProgram();
|
||||||
for (Node node : roots) {
|
for (Node node : roots) {
|
||||||
switch (node) {
|
switch (node) {
|
||||||
case ModuleNode moduleNode -> {
|
case ModuleNode moduleNode -> {
|
||||||
// 处理模块节点:遍历其中所有函数,统一用“模块名.函数名”作为全限定名,避免命名冲突
|
// 处理模块节点:遍历其中所有函数,统一用“模块名.函数名”作为全限定名
|
||||||
for (FunctionNode f : moduleNode.functions()) {
|
for (FunctionNode f : moduleNode.functions()) {
|
||||||
irProgram.add(buildFunctionWithGlobals(moduleNode, f));
|
irProgram.add(buildFunctionWithGlobals(moduleNode, f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case FunctionNode functionNode ->
|
case FunctionNode functionNode ->
|
||||||
// 处理顶层函数节点:直接构建为 IRFunction 并加入
|
// 处理顶层函数节点:直接构建为 IRFunction 并加入
|
||||||
irProgram.add(buildFunction(functionNode));
|
irProgram.add(buildFunction(functionNode));
|
||||||
case StatementNode statementNode ->
|
case StatementNode statementNode ->
|
||||||
// 处理脚本式顶层语句:封装成 "_start" 函数后构建并添加
|
// 处理脚本式顶层语句:封装成 "_start" 函数后构建并添加
|
||||||
irProgram.add(buildFunction(wrapTopLevel(statementNode)));
|
irProgram.add(buildFunction(wrapTopLevel(statementNode)));
|
||||||
default ->
|
default ->
|
||||||
// 遇到未知类型节点,抛出异常
|
// 遇到未知类型节点,抛出异常
|
||||||
@ -59,111 +56,34 @@ public final class IRProgramBuilder {
|
|||||||
return irProgram;
|
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 moduleNode 当前模块节点
|
||||||
* @param functionNode 待构建的函数节点
|
* @param functionNode 模块中的函数节点
|
||||||
* @return 包含全局声明的 IRFunction
|
* @return 包含全局声明、已加前缀函数名的 IRFunction
|
||||||
*/
|
*/
|
||||||
private IRFunction buildFunctionWithGlobals(ModuleNode moduleNode, FunctionNode functionNode) {
|
private IRFunction buildFunctionWithGlobals(ModuleNode moduleNode, FunctionNode functionNode) {
|
||||||
// 拼接模块名和函数名,生成全限定名
|
// 拼接模块名和函数名,生成全限定名
|
||||||
String qualifiedName = moduleNode.name() + "." + functionNode.name();
|
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()) {
|
if (moduleNode.globals() == null || moduleNode.globals().isEmpty()) {
|
||||||
// 无全局声明,直接重命名构建
|
|
||||||
return buildFunction(renameFunction(functionNode, qualifiedName));
|
return buildFunction(renameFunction(functionNode, qualifiedName));
|
||||||
}
|
}
|
||||||
|
// 若有全局声明,插入到函数体最前面
|
||||||
// ------- 过滤与参数重名的全局声明 -------
|
List<StatementNode> newBody = new ArrayList<>(moduleNode.globals().size() + functionNode.body().size());
|
||||||
Set<String> paramNames = new HashSet<>();
|
newBody.addAll(moduleNode.globals());
|
||||||
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());
|
newBody.addAll(functionNode.body());
|
||||||
FunctionNode wrapped = new FunctionNode(
|
FunctionNode wrapped = new FunctionNode(
|
||||||
qualifiedName,
|
qualifiedName,
|
||||||
@ -176,11 +96,11 @@ public final class IRProgramBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成一个重命名的 FunctionNode(只修改函数名,其他属性保持不变)。
|
* 生成一个重命名的 FunctionNode(只修改函数名,其他属性保持不变)。
|
||||||
*
|
*
|
||||||
* @param fn 原始函数节点
|
* @param fn 原始函数节点
|
||||||
* @param newName 新的函数名(全限定名)
|
* @param newName 新的函数名(通常为全限定名)
|
||||||
* @return 重命名后的函数节点
|
* @return 重命名后的 FunctionNode
|
||||||
*/
|
*/
|
||||||
private FunctionNode renameFunction(FunctionNode fn, String newName) {
|
private FunctionNode renameFunction(FunctionNode fn, String newName) {
|
||||||
return new FunctionNode(
|
return new FunctionNode(
|
||||||
@ -195,8 +115,8 @@ public final class IRProgramBuilder {
|
|||||||
/**
|
/**
|
||||||
* 构建 IRFunction。
|
* 构建 IRFunction。
|
||||||
*
|
*
|
||||||
* @param functionNode 待构建的 FunctionNode
|
* @param functionNode 要转换的函数节点
|
||||||
* @return 构建后的 IRFunction
|
* @return 转换结果 IRFunction
|
||||||
*/
|
*/
|
||||||
private IRFunction buildFunction(FunctionNode functionNode) {
|
private IRFunction buildFunction(FunctionNode functionNode) {
|
||||||
return new FunctionBuilder().build(functionNode);
|
return new FunctionBuilder().build(functionNode);
|
||||||
@ -204,10 +124,11 @@ public final class IRProgramBuilder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 将顶层语句节点封装成特殊的 "_start" 函数。
|
* 将顶层语句节点封装成特殊的 "_start" 函数。
|
||||||
* 主要用于脚本模式支持,使得顶层语句也可以被 IR 执行引擎统一处理。
|
* <p>
|
||||||
|
* 这对于脚本式文件支持至关重要(即文件最外层直接写语句)。
|
||||||
*
|
*
|
||||||
* @param stmt 顶层语句节点
|
* @param stmt 要封装的顶层语句
|
||||||
* @return 封装后的 FunctionNode
|
* @return 包装成 FunctionNode 的 "_start" 函数
|
||||||
*/
|
*/
|
||||||
private FunctionNode wrapTopLevel(StatementNode stmt) {
|
private FunctionNode wrapTopLevel(StatementNode stmt) {
|
||||||
return new FunctionNode(
|
return new FunctionNode(
|
||||||
|
|||||||
@ -6,16 +6,23 @@ import org.jcnc.snow.compiler.ir.value.IRConstant;
|
|||||||
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
|
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IR 指令统一生成工厂类,负责封装常量加载、二元运算、赋值、控制流等指令生成逻辑。
|
* InstructionFactory —— 统一生成并注册 IR 指令的工厂类。
|
||||||
* 提高 IR 生成阶段的可维护性与复用性。
|
* <p>
|
||||||
|
* 该类封装了常见的 IR 指令生成方式,包括常量加载、二元运算、赋值、控制流等,
|
||||||
|
* 统一简化指令插入和寄存器分配逻辑,提升 IR 生成阶段的代码可维护性和复用性。
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class InstructionFactory {
|
public class InstructionFactory {
|
||||||
|
|
||||||
|
/* ====================================================================== */
|
||||||
|
/* 常量 / 通用二元运算(新寄存器) */
|
||||||
|
/* ====================================================================== */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载整数常量,将其写入一个新分配的虚拟寄存器,并返回该寄存器。
|
* 加载整数常量,将其写入一个新分配的虚拟寄存器,并返回该寄存器。
|
||||||
*
|
*
|
||||||
* @param ctx 当前 IR 上下文
|
* @param ctx 当前 IR 上下文(用于分配寄存器与添加指令)
|
||||||
* @param value 整数常量值
|
* @param value 要加载的整数常量值
|
||||||
* @return 存储该常量的新虚拟寄存器
|
* @return 存储该常量的新虚拟寄存器
|
||||||
*/
|
*/
|
||||||
public static IRVirtualRegister loadConst(IRContext ctx, int value) {
|
public static IRVirtualRegister loadConst(IRContext ctx, int value) {
|
||||||
@ -24,150 +31,88 @@ public class InstructionFactory {
|
|||||||
return r;
|
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 ctx 当前 IR 上下文
|
||||||
* @param op 二元运算操作码
|
* @param op 运算类型(IROpCode 枚举,如 ADD_I32 等)
|
||||||
* @param a 左操作数寄存器
|
* @param a 第一个操作数寄存器
|
||||||
* @param b 右操作数寄存器
|
* @param b 第二个操作数寄存器
|
||||||
* @return 存储结果的新虚拟寄存器
|
* @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();
|
IRVirtualRegister dest = ctx.newRegister();
|
||||||
ctx.addInstruction(new BinaryOperationInstruction(op, dest, a, b));
|
ctx.addInstruction(new BinaryOperationInstruction(op, dest, a, b));
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ====================================================================== */
|
||||||
|
/* 直接写入指定寄存器 */
|
||||||
|
/* ====================================================================== */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载常量到指定寄存器。
|
* 加载整数常量到指定虚拟寄存器。
|
||||||
*
|
*
|
||||||
* @param ctx 当前 IR 上下文
|
* @param ctx 当前 IR 上下文
|
||||||
* @param dest 目标虚拟寄存器
|
* @param dest 目标寄存器
|
||||||
* @param value IR 常量值
|
* @param value 要加载的整数常量
|
||||||
*/
|
*/
|
||||||
public static void loadConstInto(IRContext ctx, IRVirtualRegister dest, IRConstant value) {
|
public static void loadConstInto(IRContext ctx, IRVirtualRegister dest, IRConstant value) {
|
||||||
ctx.addInstruction(new LoadConstInstruction(dest, value));
|
ctx.addInstruction(new LoadConstInstruction(dest, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行二元运算,并将结果写入指定寄存器。
|
* 对两个寄存器执行二元运算,将结果写入指定目标寄存器。
|
||||||
*
|
*
|
||||||
* @param ctx 当前 IR 上下文
|
* @param ctx 当前 IR 上下文
|
||||||
* @param op 二元运算操作码
|
* @param op 运算类型(IROpCode 枚举)
|
||||||
* @param a 左操作数寄存器
|
* @param a 第一个操作数寄存器
|
||||||
* @param b 右操作数寄存器
|
* @param b 第二个操作数寄存器
|
||||||
* @param dest 目标虚拟寄存器
|
* @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));
|
ctx.addInstruction(new BinaryOperationInstruction(op, dest, a, b));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成“值拷贝”语义(src → dest)。
|
* Move 指令(src → dest)。若寄存器相同也安全。
|
||||||
* 若类型无法推断,默认采用 int 方案(ADD_I32, src+0)。
|
* <p>
|
||||||
|
* 实现方式: dest = src + 0(即加上常量 0)。
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param ctx 当前 IR 上下文
|
* @param ctx 当前 IR 上下文
|
||||||
* @param src 源寄存器
|
* @param src 源寄存器
|
||||||
* @param dest 目标寄存器
|
* @param dest 目标寄存器
|
||||||
*/
|
*/
|
||||||
public static void move(IRContext ctx, IRVirtualRegister src, IRVirtualRegister dest) {
|
public static void move(IRContext ctx, IRVirtualRegister src, IRVirtualRegister dest) {
|
||||||
|
// 自赋值无需任何操作,避免生成多余的常量 0 寄存器
|
||||||
if (src == dest) {
|
if (src == dest) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String varType = ctx.getVarType(); // 需要 IRContext 提供
|
// 回退实现: dest = src + 0
|
||||||
char suffix = '\0';
|
IRVirtualRegister zero = loadConst(ctx, 0);
|
||||||
if (varType != null) {
|
ctx.addInstruction(new BinaryOperationInstruction(IROpCode.ADD_I32, dest, src, zero));
|
||||||
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 ctx 当前 IR 上下文
|
||||||
* @param label 跳转目标标签
|
* @param label 目标标签名
|
||||||
*/
|
*/
|
||||||
public static void jmp(IRContext ctx, String label) {
|
public static void jmp(IRContext ctx, String label) {
|
||||||
ctx.addInstruction(new IRJumpInstruction(label));
|
ctx.addInstruction(new IRJumpInstruction(label));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 IR 流中插入标签。
|
* 在 IR 中插入一个标签(Label)。
|
||||||
*
|
*
|
||||||
* @param ctx 当前 IR 上下文
|
* @param ctx 当前 IR 上下文
|
||||||
* @param label 标签名
|
* @param label 标签名
|
||||||
@ -177,18 +122,22 @@ public class InstructionFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 比较两个寄存器,并根据结果跳转到指定标签。
|
* 比较跳转(如 if a < b goto label),根据条件跳转到目标标签。
|
||||||
*
|
*
|
||||||
* @param ctx 当前 IR 上下文
|
* @param ctx 当前 IR 上下文
|
||||||
* @param cmp 比较操作码
|
* @param cmp 比较操作码(如 IROpCode.LT_I32 等)
|
||||||
* @param a 左操作数寄存器
|
* @param a 第一个操作数寄存器
|
||||||
* @param b 右操作数寄存器
|
* @param b 第二个操作数寄存器
|
||||||
* @param targetLabel 跳转目标标签
|
* @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));
|
ctx.addInstruction(new IRCompareJumpInstruction(cmp, a, b, targetLabel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------- 返回 ---------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成返回指令(带返回值)。
|
* 生成返回指令(带返回值)。
|
||||||
*
|
*
|
||||||
@ -200,7 +149,7 @@ public class InstructionFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成无返回值(void)返回指令。
|
* 生成无返回值的 return 指令(如 void 函数)。
|
||||||
*
|
*
|
||||||
* @param ctx 当前 IR 上下文
|
* @param ctx 当前 IR 上下文
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
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,6 +35,11 @@ public class IRFunction {
|
|||||||
*/
|
*/
|
||||||
private final List<IRVirtualRegister> parameters = new ArrayList<>();
|
private final List<IRVirtualRegister> parameters = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正式参数所对应的类型,按声明顺序排列。
|
||||||
|
*/
|
||||||
|
private final List<String> parametersType = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造一个具有指定名称的 IRFunction 实例。
|
* 构造一个具有指定名称的 IRFunction 实例。
|
||||||
*
|
*
|
||||||
@ -61,13 +66,15 @@ public class IRFunction {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param vr 表示函数某个参数的虚拟寄存器
|
* @param vr 表示函数某个参数的虚拟寄存器
|
||||||
|
* @param type 表示函数某个参数的类型
|
||||||
*/
|
*/
|
||||||
public void addParameter(IRVirtualRegister vr) {
|
public void addParameter(IRVirtualRegister vr, String type) {
|
||||||
parameters.add(vr);
|
parameters.add(vr);
|
||||||
|
parametersType.add(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取函数正式参数的只读列表。
|
* 获取函数正式参数的虚拟寄存器只读列表。
|
||||||
*
|
*
|
||||||
* @return 按声明顺序排列的虚拟寄存器列表
|
* @return 按声明顺序排列的虚拟寄存器列表
|
||||||
*/
|
*/
|
||||||
@ -75,6 +82,15 @@ public class IRFunction {
|
|||||||
return List.copyOf(parameters);
|
return List.copyOf(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取函数正式参数的类型只读列表。
|
||||||
|
*
|
||||||
|
* @return 按声明顺序排列的类型列表
|
||||||
|
*/
|
||||||
|
public List<String> parametersType() {
|
||||||
|
return List.copyOf(parametersType);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向函数体末尾追加一条 IR 指令。
|
* 向函数体末尾追加一条 IR 指令。
|
||||||
*
|
*
|
||||||
@ -130,6 +146,7 @@ public class IRFunction {
|
|||||||
.append('(');
|
.append('(');
|
||||||
for (int i = 0; i < parameters.size(); i++) {
|
for (int i = 0; i < parameters.size(); i++) {
|
||||||
sb.append(parameters.get(i));
|
sb.append(parameters.get(i));
|
||||||
|
sb.append(": ").append(parametersType.get(i));
|
||||||
if (i < parameters.size() - 1) {
|
if (i < parameters.size() - 1) {
|
||||||
sb.append(", ");
|
sb.append(", ");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ public final class ExpressionUtils {
|
|||||||
/**
|
/**
|
||||||
* 设置当前线程的默认类型后缀。
|
* 设置当前线程的默认类型后缀。
|
||||||
*
|
*
|
||||||
* @param suffix 类型后缀字符(b/s/i/l/f/d),'\0'表示无
|
* @param suffix 类型后缀字符(b/s/l/f),'\0'表示无
|
||||||
*/
|
*/
|
||||||
public static void setDefaultSuffix(char suffix) { DEFAULT_SUFFIX.set(suffix); }
|
public static void setDefaultSuffix(char suffix) { DEFAULT_SUFFIX.set(suffix); }
|
||||||
|
|
||||||
@ -47,21 +47,21 @@ public final class ExpressionUtils {
|
|||||||
// ───────────── 字面量常量解析 ─────────────
|
// ───────────── 字面量常量解析 ─────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全解析整数字面量字符串,自动去除单字符类型后缀(b/s/l/f/d,大小写均可),并转换为 int。
|
* 安全解析整数字面量字符串,自动去除单字符类型后缀(b/s/l/f,大小写均可),并转换为 int。
|
||||||
*
|
*
|
||||||
* @param literal 字面量字符串
|
* @param literal 字面量字符串
|
||||||
* @return 字面量对应的 int 数值
|
* @return 字面量对应的 int 数值
|
||||||
* @throws NumberFormatException 如果字面量无法转换为整数
|
* @throws NumberFormatException 如果字面量无法转换为整数
|
||||||
*/
|
*/
|
||||||
public static int parseIntSafely(String literal) {
|
public static int parseIntSafely(String literal) {
|
||||||
String digits = literal.replaceAll("[bslfdBSDLF]$", "");
|
String digits = literal.replaceAll("[bslfBSLF]$", "");
|
||||||
return Integer.parseInt(digits);
|
return Integer.parseInt(digits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据数字字面量字符串推断类型并生成对应的 IRConstant 常量值。
|
* 根据数字字面量字符串推断类型并生成对应的 IRConstant 常量值。
|
||||||
* <p>
|
* <p>
|
||||||
* 支持的字面量后缀有 b/s/l/f/d(大小写均可)。
|
* 支持的字面量后缀有 b/s/l/f(大小写均可)。
|
||||||
* 无后缀时,优先参考 IRContext 当前变量类型,否则根据字面量格式(含'.'或'e'等)判断为 double,否则为 int。
|
* 无后缀时,优先参考 IRContext 当前变量类型,否则根据字面量格式(含'.'或'e'等)判断为 double,否则为 int。
|
||||||
*
|
*
|
||||||
* @param ctx IRContext,允许参考变量声明类型
|
* @param ctx IRContext,允许参考变量声明类型
|
||||||
@ -73,7 +73,7 @@ public final class ExpressionUtils {
|
|||||||
: Character.toLowerCase(value.charAt(value.length() - 1));
|
: Character.toLowerCase(value.charAt(value.length() - 1));
|
||||||
|
|
||||||
String digits = switch (suffix) {
|
String digits = switch (suffix) {
|
||||||
case 'b','s','l','f','d' -> value.substring(0, value.length() - 1);
|
case 'b','s','l','f' -> value.substring(0, value.length() - 1);
|
||||||
default -> {
|
default -> {
|
||||||
// 无后缀,优先参考变量类型
|
// 无后缀,优先参考变量类型
|
||||||
if (ctx.getVarType() != null) {
|
if (ctx.getVarType() != null) {
|
||||||
@ -249,7 +249,7 @@ public final class ExpressionUtils {
|
|||||||
* @param digits 字面量字符串
|
* @param digits 字面量字符串
|
||||||
* @return 是浮点格式则返回 true
|
* @return 是浮点格式则返回 true
|
||||||
*/
|
*/
|
||||||
private static boolean looksLikeFloat(String digits) {
|
public static boolean looksLikeFloat(String digits) {
|
||||||
return digits.indexOf('.') >= 0
|
return digits.indexOf('.') >= 0
|
||||||
|| digits.indexOf('e') >= 0
|
|| digits.indexOf('e') >= 0
|
||||||
|| digits.indexOf('E') >= 0;
|
|| digits.indexOf('E') >= 0;
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
package org.jcnc.snow.compiler.lexer.core;
|
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.base.TokenScanner;
|
||||||
import org.jcnc.snow.compiler.lexer.scanners.*;
|
import org.jcnc.snow.compiler.lexer.scanners.*;
|
||||||
import org.jcnc.snow.compiler.lexer.token.Token;
|
import org.jcnc.snow.compiler.lexer.token.Token;
|
||||||
import org.jcnc.snow.compiler.lexer.token.TokenType;
|
import org.jcnc.snow.compiler.lexer.token.TokenType;
|
||||||
|
import org.jcnc.snow.compiler.lexer.utils.TokenPrinter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -54,9 +56,9 @@ public class LexerEngine {
|
|||||||
/* 2. 后置整体校验 */
|
/* 2. 后置整体校验 */
|
||||||
validateTokens();
|
validateTokens();
|
||||||
/* 3. 打印 token */
|
/* 3. 打印 token */
|
||||||
// if (SnowConfig.isDebug()) {
|
if (SnowConfig.isDebug()) {
|
||||||
// TokenPrinter.print(tokens);
|
TokenPrinter.print(tokens);
|
||||||
// }
|
}
|
||||||
|
|
||||||
/* 4. 统一报告错误 */
|
/* 4. 统一报告错误 */
|
||||||
report(errors);
|
report(errors);
|
||||||
@ -125,8 +127,8 @@ public class LexerEngine {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 目前包含2条规则: <br>
|
* 目前包含2条规则: <br>
|
||||||
* 1. Declare-Ident declare 后必须紧跟合法标识符(或 const + 标识符),并且只能一个<br>
|
* 1. Declare-Ident declare 后必须紧跟合法标识符,并且只能一个<br>
|
||||||
* 2. Double-Ident declare 后若出现第二个多余的 IDENTIFIER<br>
|
* 2. Double-Ident declare 后若出现第二个 IDENTIFIER 视为多余<br>
|
||||||
* <p>发现问题仅写入 {@link #errors},不抛异常。</p>
|
* <p>发现问题仅写入 {@link #errors},不抛异常。</p>
|
||||||
*/
|
*/
|
||||||
private void validateTokens() {
|
private void validateTokens() {
|
||||||
@ -137,28 +139,17 @@ public class LexerEngine {
|
|||||||
if (tok.getType() == TokenType.KEYWORD
|
if (tok.getType() == TokenType.KEYWORD
|
||||||
&& "declare".equalsIgnoreCase(tok.getLexeme())) {
|
&& "declare".equalsIgnoreCase(tok.getLexeme())) {
|
||||||
|
|
||||||
// 找 declare 后第一个非 NEWLINE token
|
// 第一个非 NEWLINE token
|
||||||
Token t1 = findNextNonNewline(i);
|
Token id1 = 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) {
|
if (id1 == null || id1.getType() != TokenType.IDENTIFIER) {
|
||||||
errors.add(err(
|
errors.add(err(
|
||||||
(id1 == null ? (hasConst ? t1 : tok) : id1),
|
(id1 == null ? tok : id1),
|
||||||
"declare 后必须跟合法标识符 (可选 const 关键字)"
|
"declare 后必须跟合法标识符 (以字母或 '_' 开头)"
|
||||||
));
|
));
|
||||||
continue; // 若首标识符就错,后续检查可略
|
continue; // 若首标识符就错,后续检查可略
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有第二个多余的 IDENTIFIER
|
// 检查是否有第二个 IDENTIFIER
|
||||||
Token id2 = findNextNonNewline(tokens.indexOf(id1));
|
Token id2 = findNextNonNewline(tokens.indexOf(id1));
|
||||||
if (id2 != null && id2.getType() == TokenType.IDENTIFIER) {
|
if (id2 != null && id2.getType() == TokenType.IDENTIFIER) {
|
||||||
errors.add(err(id2, "declare 声明中出现多余的标识符"));
|
errors.add(err(id2, "declare 声明中出现多余的标识符"));
|
||||||
|
|||||||
@ -28,7 +28,7 @@ public class TokenFactory {
|
|||||||
private static final Set<String> KEYWORDS = Set.of
|
private static final Set<String> KEYWORDS = Set.of
|
||||||
("module", "function", "params", "returns", "body", "end",
|
("module", "function", "params", "returns", "body", "end",
|
||||||
"if", "then", "else", "loop", "declare", "return", "import", "init",
|
"if", "then", "else", "loop", "declare", "return", "import", "init",
|
||||||
"cond", "step", "globals", "break", "continue", "const");
|
"cond", "step", "globals", "break", "continue");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内置类型名称集合,如 int、string 等。
|
* 内置类型名称集合,如 int、string 等。
|
||||||
|
|||||||
@ -10,26 +10,21 @@ import java.util.Optional;
|
|||||||
* {@code DeclarationNode} 表示抽象语法树(AST)中的变量声明语句节点。
|
* {@code DeclarationNode} 表示抽象语法树(AST)中的变量声明语句节点。
|
||||||
* <p>
|
* <p>
|
||||||
* 变量声明用于在语法层引入新的标识符及其类型信息,
|
* 变量声明用于在语法层引入新的标识符及其类型信息,
|
||||||
|
* 通常格式为 {@code type name = initializer;},其中初始化表达式可省略。
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class DeclarationNode implements StatementNode {
|
public class DeclarationNode implements StatementNode {
|
||||||
|
|
||||||
/** 声明的变量名称。 */
|
/** 声明的变量名称 */
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
/** 变量的数据类型(如 "int", "string")。 */
|
/** 变量的数据类型(如 "int", "string") */
|
||||||
private final String type;
|
private final String type;
|
||||||
|
|
||||||
/** 是否为常量声明(true 表示 const 变量,false 表示普通变量)。 */
|
/** 可选的初始化表达式 */
|
||||||
private final boolean isConst;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 可选的初始化表达式。
|
|
||||||
* 如果未指定初始化表达式,则为 Optional.empty()。
|
|
||||||
*/
|
|
||||||
private final Optional<ExpressionNode> initializer;
|
private final Optional<ExpressionNode> initializer;
|
||||||
|
|
||||||
/** 节点上下文信息(如源码中的行号、列号等)。 */
|
/** 节点上下文信息(包含行号、列号等) */
|
||||||
private final NodeContext context;
|
private final NodeContext context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,14 +32,12 @@ public class DeclarationNode implements StatementNode {
|
|||||||
*
|
*
|
||||||
* @param name 变量名称
|
* @param name 变量名称
|
||||||
* @param type 变量类型字符串(如 "int"、"string")
|
* @param type 变量类型字符串(如 "int"、"string")
|
||||||
* @param isConst 是否为常量声明
|
|
||||||
* @param initializer 可选初始化表达式,若为 {@code null} 表示未初始化
|
* @param initializer 可选初始化表达式,若为 {@code null} 表示未初始化
|
||||||
* @param context 节点上下文信息(包含行号、列号等)
|
* @param context 节点上下文信息(包含行号、列号等)
|
||||||
*/
|
*/
|
||||||
public DeclarationNode(String name, String type, boolean isConst, ExpressionNode initializer, NodeContext context) {
|
public DeclarationNode(String name, String type, ExpressionNode initializer, NodeContext context) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.isConst = isConst;
|
|
||||||
this.initializer = Optional.ofNullable(initializer);
|
this.initializer = Optional.ofNullable(initializer);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
@ -67,15 +60,6 @@ public class DeclarationNode implements StatementNode {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断该声明是否为常量(const)。
|
|
||||||
*
|
|
||||||
* @return 如果为常量声明则返回 true,否则返回 false
|
|
||||||
*/
|
|
||||||
public boolean isConst() {
|
|
||||||
return isConst;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取可选的初始化表达式。
|
* 获取可选的初始化表达式。
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package org.jcnc.snow.compiler.parser.expression;
|
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.lexer.token.Token;
|
||||||
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
|
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
|
||||||
import org.jcnc.snow.compiler.parser.ast.StringLiteralNode;
|
import org.jcnc.snow.compiler.parser.ast.StringLiteralNode;
|
||||||
@ -27,14 +26,8 @@ public class StringLiteralParselet implements PrefixParselet {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ExpressionNode parse(ParserContext ctx, Token token) {
|
public ExpressionNode parse(ParserContext ctx, Token token) {
|
||||||
// 去除首尾引号
|
|
||||||
String raw = token.getRaw();
|
String raw = token.getRaw();
|
||||||
String inner = raw.substring(1, raw.length() - 1);
|
String content = raw.substring(1, raw.length() - 1);
|
||||||
// 解析转义符与 Unicode 转义
|
return new StringLiteralNode(content, new NodeContext(token.getLine(), token.getCol(), ctx.getSourceName()));
|
||||||
String value = StringEscape.unescape(inner);
|
|
||||||
return new StringLiteralNode(
|
|
||||||
value,
|
|
||||||
new NodeContext(token.getLine(), token.getCol(), ctx.getSourceName())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,51 +34,45 @@ public class DeclarationStatementParser implements StatementParser {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DeclarationNode parse(ParserContext ctx) {
|
public DeclarationNode parse(ParserContext ctx) {
|
||||||
// 便捷引用词法 token 流
|
|
||||||
var tokens = ctx.getTokens();
|
|
||||||
|
|
||||||
// 获取当前 token 的行号、列号和文件名
|
// 获取当前 token 的行号、列号和文件名
|
||||||
int line = tokens.peek().getLine();
|
int line = ctx.getTokens().peek().getLine();
|
||||||
int column = tokens.peek().getCol();
|
int column = ctx.getTokens().peek().getCol();
|
||||||
String file = ctx.getSourceName();
|
String file = ctx.getSourceName();
|
||||||
|
|
||||||
// 声明语句必须以 "declare" 开头
|
// 声明语句必须以 "declare" 开头
|
||||||
tokens.expect("declare");
|
ctx.getTokens().expect("declare");
|
||||||
|
|
||||||
// 是否声明为常量
|
|
||||||
boolean isConst = tokens.match("const");
|
|
||||||
|
|
||||||
// 获取变量名称(标识符)
|
// 获取变量名称(标识符)
|
||||||
String name = tokens
|
String name = ctx.getTokens()
|
||||||
.expectType(TokenType.IDENTIFIER)
|
.expectType(TokenType.IDENTIFIER)
|
||||||
.getLexeme();
|
.getLexeme();
|
||||||
|
|
||||||
// 类型标注的冒号分隔符
|
// 类型标注的冒号分隔符
|
||||||
tokens.expect(":");
|
ctx.getTokens().expect(":");
|
||||||
|
|
||||||
// 获取变量类型(类型标识符)
|
// 获取变量类型(类型标识符)
|
||||||
StringBuilder type = new StringBuilder(
|
StringBuilder type = new StringBuilder(
|
||||||
tokens
|
ctx.getTokens()
|
||||||
.expectType(TokenType.TYPE)
|
.expectType(TokenType.TYPE)
|
||||||
.getLexeme()
|
.getLexeme()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 消费多维数组类型后缀 "[]"
|
// 消费多维数组类型后缀 "[]"
|
||||||
while (tokens.match("[")) {
|
while (ctx.getTokens().match("[")) {
|
||||||
tokens.expectType(TokenType.RBRACKET); // 必须配对
|
ctx.getTokens().expectType(TokenType.RBRACKET); // 必须配对
|
||||||
type.append("[]"); // 类型名称拼接 [],如 int[][] 等
|
type.append("[]"); // 类型名称拼接 [],如 int[][] 等
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可选初始化表达式(=号右侧)
|
// 可选初始化表达式(=号右侧)
|
||||||
ExpressionNode init = null;
|
ExpressionNode init = null;
|
||||||
if (tokens.match("=")) {
|
if (ctx.getTokens().match("=")) {
|
||||||
init = new PrattExpressionParser().parse(ctx);
|
init = new PrattExpressionParser().parse(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 声明语句必须以换行符结尾
|
// 声明语句必须以换行符结尾
|
||||||
tokens.expectType(TokenType.NEWLINE);
|
ctx.getTokens().expectType(TokenType.NEWLINE);
|
||||||
|
|
||||||
// 返回构建好的声明语法树节点
|
// 返回构建好的声明语法树节点
|
||||||
return new DeclarationNode(name, type.toString(), isConst, init, new NodeContext(line, column, file));
|
return new DeclarationNode(name, type.toString(), init, new NodeContext(line, column, file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import java.util.List;
|
|||||||
* <li>支持数值参数的宽化转换(如 int → double);</li>
|
* <li>支持数值参数的宽化转换(如 int → double);</li>
|
||||||
* <li>支持数值到字符串的隐式转换(自动视为调用 {@code to_string});</li>
|
* <li>支持数值到字符串的隐式转换(自动视为调用 {@code to_string});</li>
|
||||||
* <li>在发生类型不匹配、未导入模块或函数未定义等情况下记录语义错误。</li>
|
* <li>在发生类型不匹配、未导入模块或函数未定义等情况下记录语义错误。</li>
|
||||||
* <li>新增:以"_"开头的函数名只允许在本模块访问,禁止跨模块访问。</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class CallExpressionAnalyzer implements ExpressionAnalyzer<CallExpressionNode> {
|
public class CallExpressionAnalyzer implements ExpressionAnalyzer<CallExpressionNode> {
|
||||||
@ -65,42 +64,19 @@ public class CallExpressionAnalyzer implements ExpressionAnalyzer<CallExpression
|
|||||||
}
|
}
|
||||||
target = ctx.getModules().get(mod);
|
target = ctx.getModules().get(mod);
|
||||||
functionName = member;
|
functionName = member;
|
||||||
}
|
|
||||||
// 简单函数名形式: func(...)
|
// 简单函数名形式: func(...)
|
||||||
else if (callee instanceof IdentifierNode(String name, NodeContext _)) {
|
} else if (callee instanceof IdentifierNode(String name, NodeContext _)) {
|
||||||
functionName = name;
|
functionName = name;
|
||||||
}
|
|
||||||
// 不支持的 callee 形式
|
// 不支持的 callee 形式
|
||||||
else {
|
} else {
|
||||||
ctx.getErrors().add(new SemanticError(callee,
|
ctx.getErrors().add(new SemanticError(callee,
|
||||||
"不支持的调用方式: " + callee));
|
"不支持的调用方式: " + callee));
|
||||||
ctx.log("错误: 不支持的调用方式 " + callee);
|
ctx.log("错误: 不支持的调用方式 " + callee);
|
||||||
return BuiltinType.INT;
|
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<>();
|
List<Type> args = new ArrayList<>();
|
||||||
for (ExpressionNode arg : call.arguments()) {
|
for (ExpressionNode arg : call.arguments()) {
|
||||||
@ -108,6 +84,17 @@ public class CallExpressionAnalyzer implements ExpressionAnalyzer<CallExpression
|
|||||||
.analyze(ctx, mi, fn, locals, arg));
|
.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()) {
|
if (args.size() != ft.paramTypes().size()) {
|
||||||
ctx.getErrors().add(new SemanticError(call,
|
ctx.getErrors().add(new SemanticError(call,
|
||||||
|
|||||||
@ -1,94 +0,0 @@
|
|||||||
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,28 +6,22 @@ import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
|||||||
import org.jcnc.snow.compiler.semantic.core.Context;
|
import org.jcnc.snow.compiler.semantic.core.Context;
|
||||||
import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
|
import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
|
||||||
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
||||||
import org.jcnc.snow.compiler.semantic.symbol.Symbol;
|
import org.jcnc.snow.compiler.semantic.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;
|
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code AssignmentAnalyzer} 负责对赋值语句进行语义校验。
|
* {@code AssignmentAnalyzer} 是赋值语句的语义分析器。
|
||||||
*
|
* <p>
|
||||||
* <h2>校验要点</h2>
|
* 负责分析和验证赋值语句的合法性,包括:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><b>左值解析</b>:确保标识符已声明;</li>
|
* <li>变量是否已声明且可赋值(必须为 {@link SymbolKind#VARIABLE} 类型);</li>
|
||||||
* <li><b>常量保护</b>:禁止修改 {@link SymbolKind#CONSTANT};</li>
|
* <li>赋值右值的类型是否与变量类型兼容;</li>
|
||||||
* <li><b>类型兼容</b>:验证右值类型与目标类型兼容,若均为数值类型则允许宽化转换(如 <code>int → double</code>)。</li>
|
* <li>是否允许进行数值类型的自动宽化转换(如 {@code int → float})。</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
* 若类型不兼容且无法自动宽化,则将记录语义错误并输出日志信息。
|
||||||
* 任何不满足条件的情况都会向 {@link Context#getErrors()} 记录 {@link SemanticError}。
|
|
||||||
*/
|
*/
|
||||||
public class AssignmentAnalyzer implements StatementAnalyzer<AssignmentNode> {
|
public class AssignmentAnalyzer implements StatementAnalyzer<AssignmentNode> {
|
||||||
|
|
||||||
/** 错误消息前缀,统一便于搜索定位 */
|
|
||||||
private static final String ERR_PREFIX = "赋值错误: ";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分析赋值语句的语义有效性,包括左值合法性与类型匹配性。
|
* 分析赋值语句的语义有效性,包括左值合法性与类型匹配性。
|
||||||
*
|
*
|
||||||
@ -47,37 +41,29 @@ public class AssignmentAnalyzer implements StatementAnalyzer<AssignmentNode> {
|
|||||||
// 获取赋值左值变量名并进行符号解析
|
// 获取赋值左值变量名并进行符号解析
|
||||||
ctx.log("赋值检查: " + asg.variable());
|
ctx.log("赋值检查: " + asg.variable());
|
||||||
Symbol sym = locals.resolve(asg.variable());
|
Symbol sym = locals.resolve(asg.variable());
|
||||||
if (sym == null) {
|
|
||||||
ctx.getErrors().add(new SemanticError(asg,
|
|
||||||
ERR_PREFIX + "未声明的变量: " + asg.variable()));
|
|
||||||
ctx.log(ERR_PREFIX + "未声明的变量 " + asg.variable());
|
|
||||||
return; // 无需继续后续检查
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- 2. 常量不可修改 ---------- */
|
// 检查变量是否已声明且为可赋值的变量类型
|
||||||
if (sym.kind() == SymbolKind.CONSTANT) {
|
if (sym == null || sym.kind() != SymbolKind.VARIABLE) {
|
||||||
ctx.getErrors().add(new SemanticError(asg,
|
ctx.getErrors().add(new SemanticError(asg,
|
||||||
ERR_PREFIX + "无法修改常量: " + asg.variable()));
|
"未声明的变量: " + asg.variable()));
|
||||||
ctx.log(ERR_PREFIX + "尝试修改常量 " + asg.variable());
|
ctx.log("错误: 未声明的变量 " + asg.variable());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 3. 右值类型分析 ---------- */
|
// 分析右值表达式类型
|
||||||
Type rhsType = ctx.getRegistry()
|
Type valType = ctx.getRegistry().getExpressionAnalyzer(asg.value())
|
||||||
.getExpressionAnalyzer(asg.value())
|
|
||||||
.analyze(ctx, mi, fn, locals, asg.value());
|
.analyze(ctx, mi, fn, locals, asg.value());
|
||||||
|
|
||||||
/* ---------- 4. 类型兼容性检查 ---------- */
|
// 类型检查: 若类型不兼容,则尝试判断是否允许宽化转换
|
||||||
boolean compatible = sym.type().isCompatible(rhsType);
|
if (!sym.type().isCompatible(valType)) {
|
||||||
boolean widenOK = sym.type().isNumeric()
|
// 数值类型允许自动宽化转换(如 int → double)
|
||||||
&& rhsType.isNumeric()
|
if (!(sym.type().isNumeric() && valType.isNumeric()
|
||||||
&& Type.widen(rhsType, sym.type()) == sym.type();
|
&& Type.widen(valType, sym.type()) == sym.type())) {
|
||||||
|
|
||||||
if (!compatible && !widenOK) {
|
|
||||||
ctx.getErrors().add(new SemanticError(asg,
|
ctx.getErrors().add(new SemanticError(asg,
|
||||||
ERR_PREFIX + "类型不匹配: 期望 "
|
"赋值类型不匹配: 期望 " + sym.type()
|
||||||
+ sym.type() + ", 实际 " + rhsType));
|
+ ", 实际 " + valType));
|
||||||
ctx.log(ERR_PREFIX + "类型不匹配 " + asg.variable());
|
ctx.log("错误: 赋值类型不匹配 " + asg.variable());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,7 @@ import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
|||||||
import org.jcnc.snow.compiler.semantic.core.Context;
|
import org.jcnc.snow.compiler.semantic.core.Context;
|
||||||
import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
|
import org.jcnc.snow.compiler.semantic.core.ModuleInfo;
|
||||||
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
||||||
import org.jcnc.snow.compiler.semantic.symbol.Symbol;
|
import org.jcnc.snow.compiler.semantic.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.BuiltinType;
|
||||||
import org.jcnc.snow.compiler.semantic.type.Type;
|
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ import org.jcnc.snow.compiler.semantic.type.Type;
|
|||||||
public class DeclarationAnalyzer implements StatementAnalyzer<DeclarationNode> {
|
public class DeclarationAnalyzer implements StatementAnalyzer<DeclarationNode> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对单条声明语句执行语义分析。
|
* 对变量声明语句执行语义分析。
|
||||||
*
|
*
|
||||||
* @param ctx 当前语义分析上下文对象,提供类型解析、错误记录、日志输出等功能。
|
* @param ctx 当前语义分析上下文对象,提供类型解析、错误记录、日志输出等功能。
|
||||||
* @param mi 当前模块信息,支持跨模块引用检查(本分析器未直接使用)。
|
* @param mi 当前模块信息,支持跨模块引用检查(本分析器未直接使用)。
|
||||||
@ -42,53 +40,45 @@ public class DeclarationAnalyzer implements StatementAnalyzer<DeclarationNode> {
|
|||||||
SymbolTable locals,
|
SymbolTable locals,
|
||||||
DeclarationNode decl) {
|
DeclarationNode decl) {
|
||||||
|
|
||||||
/* ---------- 1. 解析类型 ---------- */
|
// 1. 解析声明类型
|
||||||
Type varType = ctx.parseType(decl.getType());
|
Type varType = ctx.parseType(decl.getType());
|
||||||
if (varType == null) {
|
if (varType == null) {
|
||||||
// 未知类型:记录错误并使用 int 兜底,避免后续空指针
|
|
||||||
ctx.getErrors().add(new SemanticError(decl,
|
ctx.getErrors().add(new SemanticError(decl,
|
||||||
"未知类型: " + decl.getType()));
|
"未知类型: " + decl.getType()));
|
||||||
ctx.log("错误: 未知类型 " + decl.getType()
|
ctx.log("错误: 未知类型 " + decl.getType()
|
||||||
+ " (声明 " + decl.getName() + ")");
|
+ " 在声明 " + decl.getName());
|
||||||
varType = BuiltinType.INT;
|
varType = BuiltinType.INT; // 容错处理: 默认降级为 int
|
||||||
}
|
}
|
||||||
ctx.log("声明" + (decl.isConst() ? "常量" : "变量")
|
ctx.log("声明变量: " + decl.getName()
|
||||||
+ ": " + decl.getName() + " 类型: " + varType);
|
+ " 类型: " + varType);
|
||||||
|
|
||||||
/* ---------- 2. 常量必须初始化 ---------- */
|
// 2. 将变量注册到当前作用域符号表中,检查重复定义
|
||||||
if (decl.isConst() && decl.getInitializer().isEmpty()) {
|
if (!locals.define(new Symbol(
|
||||||
|
decl.getName(), varType, SymbolKind.VARIABLE
|
||||||
|
))) {
|
||||||
ctx.getErrors().add(new SemanticError(decl,
|
ctx.getErrors().add(new SemanticError(decl,
|
||||||
"常量必须在声明时初始化: " + decl.getName()));
|
"变量重复声明: " + decl.getName()));
|
||||||
// 继续分析以捕获更多错误
|
ctx.log("错误: 变量重复声明 " + decl.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 3. 注册符号并检测重名 ---------- */
|
// 3. 检查初始化表达式(如果存在)
|
||||||
SymbolKind kind = decl.isConst() ? SymbolKind.CONSTANT
|
Type finalVarType = varType; // 用于 lambda 捕获
|
||||||
: SymbolKind.VARIABLE;
|
decl.getInitializer().ifPresent(init -> {
|
||||||
if (!locals.define(new Symbol(decl.getName(), varType, kind))) {
|
Type initType = ctx.getRegistry().getExpressionAnalyzer(init)
|
||||||
ctx.getErrors().add(new SemanticError(decl,
|
.analyze(ctx, mi, fn, locals, init);
|
||||||
"重复声明: " + decl.getName()));
|
|
||||||
ctx.log("错误: 重复声明 " + decl.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- 4. 校验初始化表达式(若存在) ---------- */
|
// 检查类型是否兼容,或是否允许数值宽化转换
|
||||||
Type finalVarType = varType;
|
if (!finalVarType.isCompatible(initType)) {
|
||||||
decl.getInitializer().ifPresent(initExpr -> {
|
boolean canWiden = finalVarType.isNumeric()
|
||||||
// 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()
|
&& initType.isNumeric()
|
||||||
&& Type.widen(initType, finalVarType) == finalVarType;
|
&& Type.widen(initType, finalVarType) == finalVarType;
|
||||||
|
if (!canWiden) {
|
||||||
if (!compatible && !widenOK) {
|
|
||||||
ctx.getErrors().add(new SemanticError(decl,
|
ctx.getErrors().add(new SemanticError(decl,
|
||||||
"初始化类型不匹配: 期望 " + finalVarType + ", 实际 " + initType));
|
"初始化类型不匹配: 期望 " + finalVarType
|
||||||
ctx.log("错误: 初始化类型不匹配 " + decl.getName());
|
+ ", 实际 " + initType));
|
||||||
|
ctx.log("错误: 初始化类型不匹配 "
|
||||||
|
+ decl.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package org.jcnc.snow.compiler.semantic.analyzers.statement;
|
package org.jcnc.snow.compiler.semantic.analyzers.statement;
|
||||||
|
|
||||||
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
|
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.parser.ast.ReturnNode;
|
||||||
import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
||||||
import org.jcnc.snow.compiler.semantic.core.Context;
|
import org.jcnc.snow.compiler.semantic.core.Context;
|
||||||
@ -11,6 +12,9 @@ import org.jcnc.snow.compiler.semantic.type.BuiltinType;
|
|||||||
import org.jcnc.snow.compiler.semantic.type.FunctionType;
|
import org.jcnc.snow.compiler.semantic.type.FunctionType;
|
||||||
import org.jcnc.snow.compiler.semantic.type.Type;
|
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code ReturnAnalyzer} 是用于分析 {@link ReturnNode} 返回语句的语义分析器。
|
* {@code ReturnAnalyzer} 是用于分析 {@link ReturnNode} 返回语句的语义分析器。
|
||||||
* <p>
|
* <p>
|
||||||
@ -42,12 +46,32 @@ public class ReturnAnalyzer implements StatementAnalyzer<ReturnNode> {
|
|||||||
|
|
||||||
ctx.log("检查 return");
|
ctx.log("检查 return");
|
||||||
|
|
||||||
// 获取当前函数的定义信息
|
// 获取当前函数的重载列表
|
||||||
FunctionType expected = ctx.getModules()
|
var overloading = ctx.getModules()
|
||||||
.get(mi.getName())
|
.get(mi.getName())
|
||||||
.getFunctions()
|
.getFunctions()
|
||||||
.get(fn.name());
|
.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: 存在返回表达式,需进行类型检查
|
// 情况 1: 存在返回表达式,需进行类型检查
|
||||||
ret.getExpression().ifPresentOrElse(exp -> {
|
ret.getExpression().ifPresentOrElse(exp -> {
|
||||||
var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(exp);
|
var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(exp);
|
||||||
|
|||||||
@ -65,8 +65,8 @@ public final class AnalyzerRegistrar {
|
|||||||
// ---------- 注册一元表达式分析器 ----------
|
// ---------- 注册一元表达式分析器 ----------
|
||||||
registry.registerExpressionAnalyzer(UnaryExpressionNode.class, new UnaryExpressionAnalyzer());
|
registry.registerExpressionAnalyzer(UnaryExpressionNode.class, new UnaryExpressionAnalyzer());
|
||||||
|
|
||||||
// ---------- 成员访问表达式 ----------
|
// 对尚未实现的表达式类型使用兜底处理器
|
||||||
registry.registerExpressionAnalyzer(MemberExpressionNode.class,
|
registry.registerExpressionAnalyzer(MemberExpressionNode.class,
|
||||||
new MemberExpressionAnalyzer());
|
new UnsupportedExpressionAnalyzer<>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,14 +62,54 @@ public final class BuiltinTypeRegistry {
|
|||||||
/* ---------- 注册标准库 os ---------- */
|
/* ---------- 注册标准库 os ---------- */
|
||||||
ModuleInfo utils = new ModuleInfo("os");
|
ModuleInfo utils = new ModuleInfo("os");
|
||||||
|
|
||||||
// syscall(string, int): void —— 供标准库内部使用的调用接口
|
/* syscall(string, type): void 的一组重载 */
|
||||||
utils.getFunctions().put(
|
List<FunctionType> overloading = new ArrayList<>();
|
||||||
"syscall",
|
{
|
||||||
new FunctionType(
|
// 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(
|
||||||
Arrays.asList(BuiltinType.STRING, BuiltinType.INT),
|
Arrays.asList(BuiltinType.STRING, BuiltinType.INT),
|
||||||
BuiltinType.VOID
|
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 到上下文的模块表(若已存在则不重复添加)
|
// 注册 BuiltinUtils 到上下文的模块表(若已存在则不重复添加)
|
||||||
ctx.getModules().putIfAbsent("os", utils);
|
ctx.getModules().putIfAbsent("os", utils);
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package org.jcnc.snow.compiler.semantic.core;
|
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.FunctionNode;
|
||||||
import org.jcnc.snow.compiler.parser.ast.ModuleNode;
|
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.parser.ast.ReturnNode;
|
||||||
import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer;
|
||||||
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
import org.jcnc.snow.compiler.semantic.error.SemanticError;
|
||||||
@ -11,85 +11,70 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolKind;
|
|||||||
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
|
import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
|
||||||
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
|
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code FunctionChecker} 是 Snow 编译器语义分析阶段用于检查所有函数体合法性的总控调度器。
|
* {@code FunctionChecker} 是语义分析阶段中用于检查函数体语句合法性的调度器。
|
||||||
* <p>
|
* <p>
|
||||||
* <b>设计核心:</b>采用“两遍扫描”方案,彻底解决跨模块全局变量/常量类型推断和引用依赖问题:
|
* 它逐个遍历所有模块中的函数定义,并对函数体中的每一条语句调用对应的语义分析器,
|
||||||
|
* 执行类型检查、作用域验证、错误记录等任务。
|
||||||
|
* <p>
|
||||||
|
* 核心职责包括:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><b>第一遍</b>:为所有模块预先构建并注册其全局符号表(globals),保证跨模块引用时可见。</li>
|
* <li>为每个函数构建局部符号表并注册函数参数为变量;</li>
|
||||||
* <li><b>第二遍</b>:在全局符号表全部就绪后,依次分析所有模块的函数体,实现局部作用域、类型推断、语义校验等任务。</li>
|
* <li>分发函数体语句至相应的 {@link StatementAnalyzer};</li>
|
||||||
* </ul>
|
* <li>记录未支持语句类型为语义错误;</li>
|
||||||
* <b>功能职责:</b>
|
* <li>依赖上下文 {@link Context} 提供模块信息、类型解析、错误收集等服务。</li>
|
||||||
* <ul>
|
|
||||||
* <li>遍历所有模块,先建立 globals,再遍历并检查所有函数体语句。</li>
|
|
||||||
* <li>为每个函数体构建完整符号表,并注册参数变量。</li>
|
|
||||||
* <li>分发每条语句到对应 {@link StatementAnalyzer} 进行类型检查和错误校验。</li>
|
|
||||||
* <li>自动检查非 void 函数 return 完备性。</li>
|
|
||||||
* <li>记录所有语义错误,便于前端高亮和诊断。</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param ctx 全局语义分析上下文,持有模块信息、符号表、错误收集等资源
|
* @param ctx 全局语义分析上下文,提供模块信息、注册表、错误记录等支持
|
||||||
*/
|
*/
|
||||||
public record FunctionChecker(Context ctx) {
|
public record FunctionChecker(Context ctx) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主入口:对所有模块的所有函数体进行语义检查(两遍扫描实现)。
|
* 构造函数体检查器。
|
||||||
|
*
|
||||||
|
* @param ctx 当前语义分析上下文
|
||||||
|
*/
|
||||||
|
public FunctionChecker {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行函数体检查流程。
|
||||||
* <p>
|
* <p>
|
||||||
* <b>第一遍</b>:为每个模块提前构建全局符号表(包含本模块所有全局变量和常量),
|
* 对所有模块中的所有函数依次进行处理:
|
||||||
* 并注册到 {@link ModuleInfo},确保跨模块引用时所有全局符号都已可用。
|
* <ol>
|
||||||
* <br>
|
* <li>查找模块对应的 {@link ModuleInfo};</li>
|
||||||
* <b>第二遍</b>:遍历所有模块的所有函数,对每个函数体:
|
* <li>创建函数局部符号表 {@link SymbolTable},并注册所有参数变量;</li>
|
||||||
* <ul>
|
* <li>对函数体中的每一条语句分发到已注册的分析器进行语义分析;</li>
|
||||||
* <li>构建局部作用域,父作用域为对应模块的 globals;</li>
|
* <li>若某条语句无可用分析器,则记录为 {@link SemanticError}。</li>
|
||||||
* <li>注册参数变量;</li>
|
* </ol>
|
||||||
* <li>依次分发每条语句到对应 {@link StatementAnalyzer},进行类型和语义检查;</li>
|
|
||||||
* <li>自动校验非 void 函数 return 完备性;</li>
|
|
||||||
* <li>将所有发现的问题统一记录到 {@link SemanticError} 列表。</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
*
|
||||||
* @param mods 所有模块的 AST 根节点集合
|
* @param mods 所有模块的 AST 根节点集合
|
||||||
*/
|
*/
|
||||||
public void check(Iterable<ModuleNode> mods) {
|
public void check(Iterable<ModuleNode> mods) {
|
||||||
List<ModuleNode> moduleList = new ArrayList<>();
|
|
||||||
// ---------- 第1遍:收集所有全局符号表 ----------
|
|
||||||
for (ModuleNode mod : mods) {
|
for (ModuleNode mod : mods) {
|
||||||
moduleList.add(mod);
|
// 获取当前模块对应的语义信息
|
||||||
|
|
||||||
// 获取当前模块的元信息
|
|
||||||
ModuleInfo mi = ctx.modules().get(mod.name());
|
ModuleInfo mi = ctx.modules().get(mod.name());
|
||||||
// 创建本模块全局作用域(无父作用域)
|
|
||||||
SymbolTable globalScope = new SymbolTable(null);
|
|
||||||
|
|
||||||
// 注册所有全局变量/常量到符号表
|
// 先构建全局符号表
|
||||||
|
SymbolTable globalScope = new SymbolTable(null);
|
||||||
for (DeclarationNode g : mod.globals()) {
|
for (DeclarationNode g : mod.globals()) {
|
||||||
var t = ctx.parseType(g.getType());
|
var t = ctx.parseType(g.getType());
|
||||||
SymbolKind k = g.isConst() ? SymbolKind.CONSTANT : SymbolKind.VARIABLE;
|
// 检查全局变量是否重复声明
|
||||||
String dupType = g.isConst() ? "常量" : "变量";
|
if (!globalScope.define(new Symbol(g.getName(), t, SymbolKind.VARIABLE))) {
|
||||||
// 检查重复声明
|
|
||||||
if (!globalScope.define(new Symbol(g.getName(), t, k))) {
|
|
||||||
ctx.errors().add(new SemanticError(
|
ctx.errors().add(new SemanticError(
|
||||||
g,
|
g,
|
||||||
dupType + "重复声明: " + g.getName()
|
"全局变量重复声明: " + 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()) {
|
for (FunctionNode fn : mod.functions()) {
|
||||||
// 构建函数局部作用域,父作用域为 globalScope
|
|
||||||
|
// 构建函数局部作用域符号表,父作用域为 globalScope
|
||||||
SymbolTable locals = new SymbolTable(globalScope);
|
SymbolTable locals = new SymbolTable(globalScope);
|
||||||
|
|
||||||
// 注册函数参数为局部变量
|
// 将函数参数注册为局部变量
|
||||||
fn.parameters().forEach(p ->
|
fn.parameters().forEach(p ->
|
||||||
locals.define(new Symbol(
|
locals.define(new Symbol(
|
||||||
p.name(),
|
p.name(),
|
||||||
@ -98,7 +83,7 @@ public record FunctionChecker(Context ctx) {
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
// 分析函数体内每条语句
|
// 遍历并分析函数体内的每条语句
|
||||||
for (var stmt : fn.body()) {
|
for (var stmt : fn.body()) {
|
||||||
var analyzer = ctx.getRegistry().getStatementAnalyzer(stmt);
|
var analyzer = ctx.getRegistry().getStatementAnalyzer(stmt);
|
||||||
if (analyzer != null) {
|
if (analyzer != null) {
|
||||||
|
|||||||
@ -1,54 +1,33 @@
|
|||||||
package org.jcnc.snow.compiler.semantic.core;
|
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.FunctionType;
|
||||||
|
import org.jcnc.snow.compiler.semantic.type.Type;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code ModuleInfo} 表示单个模块在语义分析阶段的元信息封装。
|
* {@code ModuleInfo} 表示单个模块在语义分析阶段的元信息封装。
|
||||||
* <p>
|
* <p>
|
||||||
* 用于在分析期间管理模块间依赖、函数签名查找、全局符号表等关键任务。
|
* 用于在分析期间管理模块间依赖、函数签名查找等关键任务。
|
||||||
* 每个模块对应一个唯一的 {@code ModuleInfo} 实例,贯穿整个语义分析流程。
|
* 每个模块对应一个唯一的 {@code ModuleInfo} 实例。
|
||||||
*
|
* <p>
|
||||||
* <p><b>包含信息:</b>
|
* 包含信息包括:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>模块名称(全局唯一标识);</li>
|
* <li>模块名称(唯一标识);</li>
|
||||||
* <li>该模块导入的其他模块名集合(跨模块引用支持);</li>
|
* <li>该模块导入的其他模块名集合;</li>
|
||||||
* <li>该模块中定义的所有函数签名 {@code Map<String, FunctionType>};</li>
|
* <li>该模块中定义的所有函数签名 {@code Map<String, FunctionType>}。</li>
|
||||||
* <li>模块级全局符号表 {@link SymbolTable}(常量 / 全局变量,支持跨模块类型推断)。</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p><b>典型用途:</b>
|
|
||||||
* <ul>
|
|
||||||
* <li>用于函数签名类型查找、重名检测、跨模块引用校验等;</li>
|
|
||||||
* <li>全局符号表为类型检查与后端 IR 常量折叠等模块级分析提供支撑。</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class ModuleInfo {
|
public class ModuleInfo {
|
||||||
|
|
||||||
/**
|
/** 模块名称,作为全局唯一标识 */
|
||||||
* 模块名称,作为全局唯一标识
|
|
||||||
*/
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
/**
|
/** 该模块显式导入的模块名集合(用于跨模块访问符号) */
|
||||||
* 该模块显式导入的模块名集合(用于跨模块访问符号)
|
|
||||||
*/
|
|
||||||
private final Set<String> imports = new HashSet<>();
|
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造模块信息对象。
|
* 构造模块信息对象。
|
||||||
@ -71,7 +50,7 @@ public class ModuleInfo {
|
|||||||
/**
|
/**
|
||||||
* 获取该模块导入的模块名称集合。
|
* 获取该模块导入的模块名称集合。
|
||||||
* <p>
|
* <p>
|
||||||
* 返回集合为内部数据的直接引用,调用方可通过 {@code add}/{@code remove} 方法动态维护导入信息。
|
* 返回集合为内部数据的直接引用,调用方可通过 {@code add/remove} 方法动态维护导入信息。
|
||||||
*
|
*
|
||||||
* @return 可变集合,包含所有导入模块名
|
* @return 可变集合,包含所有导入模块名
|
||||||
*/
|
*/
|
||||||
@ -87,30 +66,57 @@ public class ModuleInfo {
|
|||||||
*
|
*
|
||||||
* @return 模块内函数定义映射表
|
* @return 模块内函数定义映射表
|
||||||
*/
|
*/
|
||||||
public Map<String, FunctionType> getFunctions() {
|
public Map<String, List<FunctionType>> getFunctions() {
|
||||||
return functions;
|
return functions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取模块的全局符号表(包含常量与全局变量)。
|
* 添加一个新的函数类型
|
||||||
* <p>
|
|
||||||
* 该符号表由语义分析的 FunctionChecker 阶段构建完成并注入。
|
|
||||||
* 提供跨模块类型检查、常量折叠等能力。
|
|
||||||
*
|
*
|
||||||
* @return 当前模块的全局符号表
|
* @param name 函数名
|
||||||
|
* @param ft 函数类型
|
||||||
*/
|
*/
|
||||||
public SymbolTable getGlobals() {
|
public void addFunction(String name, FunctionType ft) {
|
||||||
return globals;
|
if (!functions.containsKey(name)) {
|
||||||
|
functions.put(name, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
functions.get(name).add(ft);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置模块的全局符号表。
|
* 判断函数类型是否存在
|
||||||
* <p>
|
|
||||||
* 仅应由 FunctionChecker 在语义分析全局扫描阶段调用。
|
|
||||||
*
|
*
|
||||||
* @param globals 全局符号表实例
|
* @param name 函数名
|
||||||
|
* @param ft 函数类型
|
||||||
|
* @return 函数类型存在则返回 true,否则 false
|
||||||
*/
|
*/
|
||||||
public void setGlobals(SymbolTable globals) {
|
public boolean existsFunction(String name, FunctionType ft) {
|
||||||
this.globals = globals;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,8 +78,17 @@ public record SignatureRegistrar(Context ctx) {
|
|||||||
Type ret = Optional.ofNullable(ctx.parseType(fn.returnType()))
|
Type ret = Optional.ofNullable(ctx.parseType(fn.returnType()))
|
||||||
.orElse(BuiltinType.VOID);
|
.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.getFunctions().put(fn.name(), new FunctionType(params, ret));
|
mi.addFunction(fn.name(), ft);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,47 +3,31 @@ package org.jcnc.snow.compiler.semantic.symbol;
|
|||||||
/**
|
/**
|
||||||
* {@code SymbolKind} 枚举用于标识符号表中不同类型的命名实体。
|
* {@code SymbolKind} 枚举用于标识符号表中不同类型的命名实体。
|
||||||
* <p>
|
* <p>
|
||||||
* 在语义分析过程中,编译器需要根据符号的种类(Kind)采用不同的处理策略:
|
* 在语义分析过程中,编译器需要根据符号的种类(Kind)采用不同的处理策略:
|
||||||
* 例如变量参与类型推导、函数用于调用匹配、模块用于跨作用域引用等。
|
* 例如变量参与类型推导、函数用于调用匹配、模块用于跨作用域引用等。
|
||||||
* </p>
|
|
||||||
* <p>
|
* <p>
|
||||||
* 当前支持的符号种类包括:
|
* 当前支持的符号种类包括:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #VARIABLE}: 变量符号(局部变量、全局变量、成员变量等);</li>
|
* <li>{@link #VARIABLE}: 变量符号(局部变量、全局变量、成员变量等);</li>
|
||||||
* <li>{@link #CONSTANT}: 常量符号(只读、不可变的公开常量);</li>
|
|
||||||
* <li>{@link #FUNCTION}: 函数符号(自由函数、方法、构造函数等);</li>
|
* <li>{@link #FUNCTION}: 函数符号(自由函数、方法、构造函数等);</li>
|
||||||
* <li>{@link #MODULE}: 模块符号(代表命名空间、库或逻辑模块);</li>
|
* <li>{@link #MODULE}: 模块符号(代表命名空间、库或逻辑模块);</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
|
||||||
* 可根据需求扩展更多符号种类。
|
|
||||||
* </p>
|
|
||||||
*/
|
*/
|
||||||
public enum SymbolKind {
|
public enum SymbolKind {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 变量符号,表示在作用域中声明的可赋值实体。
|
* 变量符号,表示在作用域中声明的可赋值实体。
|
||||||
* <p>
|
* <p>
|
||||||
* 包括函数参数、局部变量、全局变量、成员变量等。
|
* 包括函数参数、局部变量、全局变量、常量等,
|
||||||
* 分析器会基于其类型参与表达式类型校验和赋值检查。
|
* 分析器会基于其类型参与表达式类型校验和赋值检查。
|
||||||
* </p>
|
|
||||||
*/
|
*/
|
||||||
VARIABLE,
|
VARIABLE,
|
||||||
|
|
||||||
/**
|
|
||||||
* 常量符号,表示只读、不可变的公开常量。
|
|
||||||
* <p>
|
|
||||||
* 常量在声明后不可被修改,仅可读取。常用于定义全局、模块级的常量值,
|
|
||||||
* 在类型推断、常量折叠等语义分析过程中单独处理。
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
CONSTANT,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 函数符号,表示可调用的过程实体。
|
* 函数符号,表示可调用的过程实体。
|
||||||
* <p>
|
* <p>
|
||||||
* 包括普通函数、方法、构造器等。
|
* 包括普通函数、方法、构造器等。
|
||||||
* 用于函数签名注册、函数调用检查及返回值推导。
|
* 用于函数签名注册、函数调用检查及返回值推导。
|
||||||
* </p>
|
|
||||||
*/
|
*/
|
||||||
FUNCTION,
|
FUNCTION,
|
||||||
|
|
||||||
@ -51,7 +35,6 @@ public enum SymbolKind {
|
|||||||
* 模块符号,表示一个命名空间或模块单元。
|
* 模块符号,表示一个命名空间或模块单元。
|
||||||
* <p>
|
* <p>
|
||||||
* 在跨模块调用、导入语句校验、作用域隔离中发挥作用。
|
* 在跨模块调用、导入语句校验、作用域隔离中发挥作用。
|
||||||
* </p>
|
|
||||||
*/
|
*/
|
||||||
MODULE
|
MODULE
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,9 +31,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class GenerateTask implements Task {
|
public final class GenerateTask implements Task {
|
||||||
|
|
||||||
/**
|
/** 项目信息元数据 */
|
||||||
* 项目信息元数据
|
|
||||||
*/
|
|
||||||
private final Project project;
|
private final Project project;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,13 +90,6 @@ public final class GenerateTask implements Task {
|
|||||||
System.out.println("[generate] created " + root.relativize(mainSnow));
|
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.");
|
System.out.println("[generate] project scaffold is ready.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package org.jcnc.snow.pkg.utils;
|
package org.jcnc.snow.pkg.utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 示例模块模板工具类,提供标准的示例 Snow 代码片段。
|
* 示例模块模板工具类,提供 main.snow 的标准示例代码字符串。
|
||||||
* <p>
|
* <p>
|
||||||
* 用于项目脚手架生成或帮助用户快速上手 .snow 语言。
|
* 用于项目脚手架生成或帮助用户快速上手 .snow 语言。
|
||||||
* </p>
|
* </p>
|
||||||
@ -23,11 +23,12 @@ public final class SnowExample {
|
|||||||
public static String getMainModule() {
|
public static String getMainModule() {
|
||||||
return """
|
return """
|
||||||
module: Math
|
module: Math
|
||||||
import: os
|
|
||||||
function: main
|
function: main
|
||||||
returns: void
|
params:
|
||||||
|
returns: int
|
||||||
body:
|
body:
|
||||||
os.print(Math.factorial(6))
|
Math.factorial(6)
|
||||||
|
return 0
|
||||||
end body
|
end body
|
||||||
end function
|
end function
|
||||||
|
|
||||||
@ -54,25 +55,4 @@ public final class SnowExample {
|
|||||||
end module
|
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,54 +10,36 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@code RPushCommand} class implements the {@link Command} interface and provides
|
* The {@code RPushCommand} class implements the {@link Command} interface
|
||||||
* the "reference push" instruction ({@code R_PUSH}) for the virtual machine.
|
* and represents the "reference push" instruction ({@code R_PUSH}) in the virtual machine.
|
||||||
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Function:</b> Pushes a reference-type value (String literal or array literal) onto the operand stack.
|
* This instruction pushes a reference-type value onto the operand stack.
|
||||||
|
* The input is parsed from the textual instruction form, which can represent:
|
||||||
|
* <ul>
|
||||||
|
* <li>String literals</li>
|
||||||
|
* <li>Array literals (e.g., {@code [1, 2, 3]}), including nested arrays</li>
|
||||||
|
* </ul>
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Supported Literals</h2>
|
* <p>
|
||||||
* <ul>
|
* For array literals, a nested list structure is constructed. In this implementation,
|
||||||
* <li><b>String Literals:</b> Quoted strings (e.g., {@code "hello\nworld"}) with escape sequence support.</li>
|
* array literals are pushed as <b>mutable</b> {@link java.util.ArrayList} structures,
|
||||||
* <li><b>Array Literals:</b> Bracketed array forms (e.g., {@code [1, 2, [3, 4]]}), including nested arrays.</li>
|
* so that subsequent system calls such as {@code ARR_SET} can modify elements in-place.
|
||||||
* </ul>
|
* </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 {
|
public class RPushCommand implements Command {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the {@code R_PUSH} instruction. Parses the given literal parameter and pushes it onto the operand stack.
|
* Executes the R_PUSH command.
|
||||||
* <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 instruction split into parts (opcode and arguments)
|
* @param parts The parts of the instruction, where {@code parts[1..n]} are concatenated as the literal.
|
||||||
* @param pc The current program counter
|
* @param pc The current program counter.
|
||||||
* @param stack The operand stack to push the value onto
|
* @param stack The operand stack where the result will be pushed.
|
||||||
* @param local The local variable store (unused)
|
* @param local The local variable store (unused in this instruction).
|
||||||
* @param callStack The call stack (unused)
|
* @param callStack The call stack (unused in this instruction).
|
||||||
* @return The next program counter (pc + 1)
|
* @return The new program counter (typically {@code pc+1}).
|
||||||
* @throws IllegalStateException if the R_PUSH parameter is missing or parsing fails
|
* @throws IllegalStateException if no literal parameter is provided.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int execute(String[] parts, int pc, OperandStack stack, LocalVariableStore local, CallStack callStack) {
|
public int execute(String[] parts, int pc, OperandStack stack, LocalVariableStore local, CallStack callStack) {
|
||||||
@ -72,64 +54,71 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
String literal = sb.toString().trim();
|
String literal = sb.toString().trim();
|
||||||
|
|
||||||
// Handle array literal
|
// Check if this is an array literal
|
||||||
if (literal.startsWith("[") && literal.endsWith("]")) {
|
if (literal.startsWith("[") && literal.endsWith("]")) {
|
||||||
Object parsed = parseValue(new Cursor(literal));
|
Object parsed = parseValue(new Cursor(literal));
|
||||||
if (!(parsed instanceof List<?> list)) {
|
if (!(parsed instanceof List<?> list)) {
|
||||||
|
// Should not happen in theory; safety fallback
|
||||||
stack.push(parsed);
|
stack.push(parsed);
|
||||||
} else {
|
} else {
|
||||||
|
// Push a deep-mutable copy so ARR_SET can modify elements in-place
|
||||||
stack.push(deepMutable(list));
|
stack.push(deepMutable(list));
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
// String literal with quotes and escapes
|
// Regular string, push as-is
|
||||||
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);
|
stack.push(literal);
|
||||||
}
|
}
|
||||||
return pc + 1;
|
return pc + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for string parsing, used by the array and string literal parsers.
|
* A simple string cursor, supporting index increment and character reading, for use by the parser.
|
||||||
*/
|
*/
|
||||||
static class Cursor {
|
static class Cursor {
|
||||||
final String s;
|
final String s;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a cursor over the provided string.
|
* Constructs a new {@code Cursor} for the given string.
|
||||||
* @param s the input string to parse
|
*
|
||||||
|
* @param s The 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.
|
* Advances the cursor by one character.
|
||||||
*/
|
*/
|
||||||
void skip() { i++; }
|
void skip() {
|
||||||
|
i++;
|
||||||
/**
|
|
||||||
* Returns true if the cursor has reached the end of the string.
|
|
||||||
* @return true if end of string
|
|
||||||
*/
|
|
||||||
boolean end() { return i >= s.length(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current character at the cursor position.
|
|
||||||
* @return the current character
|
|
||||||
*/
|
|
||||||
char ch() { return s.charAt(i); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a value from the current cursor position.
|
* @return {@code true} if the cursor has reached the end of the string.
|
||||||
* Supports arrays, quoted strings, or atoms.
|
*/
|
||||||
|
boolean end() {
|
||||||
|
return i >= s.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the character at the current cursor position.
|
||||||
*
|
*
|
||||||
* @param c the parsing cursor
|
* @return current character
|
||||||
* @return the parsed object (List, String, Number, Boolean, or String fallback)
|
* @throws StringIndexOutOfBoundsException if at end of string
|
||||||
|
*/
|
||||||
|
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).
|
||||||
|
*
|
||||||
|
* @param c The cursor for parsing.
|
||||||
|
* @return The parsed value (could be List, String, Number).
|
||||||
*/
|
*/
|
||||||
Object parseValue(Cursor c) {
|
Object parseValue(Cursor c) {
|
||||||
skipWs(c);
|
skipWs(c);
|
||||||
@ -141,8 +130,9 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips whitespace characters at the cursor.
|
* Skips whitespace characters in the input string.
|
||||||
* @param c the parsing cursor
|
*
|
||||||
|
* @param c The cursor to advance.
|
||||||
*/
|
*/
|
||||||
private static void skipWs(Cursor c) {
|
private static void skipWs(Cursor c) {
|
||||||
while (!c.end()) {
|
while (!c.end()) {
|
||||||
@ -153,13 +143,13 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an array literal of the form [elem1, elem2, ...] (may be nested).
|
* Parses an array literal from the input, including nested arrays.
|
||||||
* Recursively parses elements using {@link #parseValue(Cursor)}.
|
|
||||||
*
|
*
|
||||||
* @param c the parsing cursor
|
* @param c The cursor (positioned at '[' at entry).
|
||||||
* @return a List of parsed elements
|
* @return A List representing the parsed array.
|
||||||
*/
|
*/
|
||||||
private Object parseArray(Cursor c) {
|
private Object parseArray(Cursor c) {
|
||||||
|
// assumes current char is '['
|
||||||
c.skip(); // skip '['
|
c.skip(); // skip '['
|
||||||
List<Object> out = new ArrayList<>();
|
List<Object> out = new ArrayList<>();
|
||||||
skipWs(c);
|
skipWs(c);
|
||||||
@ -180,12 +170,13 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a quoted string, handling standard Java escape sequences (e.g. \n, \t, uXXXX).
|
* Parses a quoted string literal, handling escape characters.
|
||||||
*
|
*
|
||||||
* @param c the parsing cursor
|
* @param c The cursor (positioned at '"' at entry).
|
||||||
* @return the decoded string
|
* @return The parsed string value.
|
||||||
*/
|
*/
|
||||||
private static String parseQuoted(Cursor c) {
|
private static String parseQuoted(Cursor c) {
|
||||||
|
// assumes current char is '"'
|
||||||
c.skip(); // skip opening quote
|
c.skip(); // skip opening quote
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
while (!c.end()) {
|
while (!c.end()) {
|
||||||
@ -199,25 +190,8 @@ public class RPushCommand implements Command {
|
|||||||
case 'n' -> sb.append('\n');
|
case 'n' -> sb.append('\n');
|
||||||
case 'r' -> sb.append('\r');
|
case 'r' -> sb.append('\r');
|
||||||
case 't' -> sb.append('\t');
|
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 '\\' -> 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);
|
default -> sb.append(esc);
|
||||||
}
|
}
|
||||||
} else if (ch == '\"') {
|
} else if (ch == '\"') {
|
||||||
@ -230,10 +204,10 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an atomic value (number, boolean, or fallback string) from the cursor.
|
* Parses an atom (number, hexadecimal, binary, or plain string token).
|
||||||
*
|
*
|
||||||
* @param c the parsing cursor
|
* @param c The cursor.
|
||||||
* @return the parsed object (Integer, Double, Float, Long, Boolean, or String)
|
* @return An Integer, Double, or String, depending on the content.
|
||||||
*/
|
*/
|
||||||
private static Object parseAtom(Cursor c) {
|
private static Object parseAtom(Cursor c) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@ -244,7 +218,7 @@ public class RPushCommand implements Command {
|
|||||||
c.skip();
|
c.skip();
|
||||||
}
|
}
|
||||||
String token = sb.toString();
|
String token = sb.toString();
|
||||||
// Try number parsing with various notations and types
|
// try number
|
||||||
try {
|
try {
|
||||||
if (token.startsWith("0x") || token.startsWith("0X")) {
|
if (token.startsWith("0x") || token.startsWith("0X")) {
|
||||||
return Integer.parseInt(token.substring(2), 16);
|
return Integer.parseInt(token.substring(2), 16);
|
||||||
@ -252,20 +226,6 @@ public class RPushCommand implements Command {
|
|||||||
if (token.startsWith("0b") || token.startsWith("0B")) {
|
if (token.startsWith("0b") || token.startsWith("0B")) {
|
||||||
return Integer.parseInt(token.substring(2), 2);
|
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(".")) {
|
if (token.contains(".")) {
|
||||||
return Double.parseDouble(token);
|
return Double.parseDouble(token);
|
||||||
}
|
}
|
||||||
@ -276,11 +236,13 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------- helpers for immutability/mutability ----------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a deeply unmodifiable version of the provided list (and its nested lists).
|
* Recursively creates an unmodifiable copy of a list, with all nested lists also unmodifiable.
|
||||||
*
|
*
|
||||||
* @param l the original list
|
* @param l The list to make unmodifiable.
|
||||||
* @return an unmodifiable view of the list and all nested lists
|
* @return An unmodifiable deep copy of the list.
|
||||||
*/
|
*/
|
||||||
List<?> deepUnmodifiable(List<?> l) {
|
List<?> deepUnmodifiable(List<?> l) {
|
||||||
List<Object> out = new ArrayList<>(l.size());
|
List<Object> out = new ArrayList<>(l.size());
|
||||||
@ -289,10 +251,10 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for {@link #deepUnmodifiable(List)}; handles nested lists recursively.
|
* Helper method for {@link #deepUnmodifiable(List)}. Recursively processes each element.
|
||||||
*
|
*
|
||||||
* @param v the object to process
|
* @param v The object to process.
|
||||||
* @return an unmodifiable list if input is a list; otherwise, the object itself
|
* @return Unmodifiable list if input is a list, otherwise the value itself.
|
||||||
*/
|
*/
|
||||||
Object deepUnmodifiableObject(Object v) {
|
Object deepUnmodifiableObject(Object v) {
|
||||||
if (v instanceof List<?> l) {
|
if (v instanceof List<?> l) {
|
||||||
@ -302,10 +264,11 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a deeply mutable version of the provided list (and its nested lists).
|
* 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.
|
||||||
*
|
*
|
||||||
* @param l the original list
|
* @param l The source list.
|
||||||
* @return a new mutable list (ArrayList), with all nested lists mutable
|
* @return Deep mutable copy of the list.
|
||||||
*/
|
*/
|
||||||
private static java.util.List<?> deepMutable(java.util.List<?> l) {
|
private static java.util.List<?> deepMutable(java.util.List<?> l) {
|
||||||
java.util.List<Object> out = new java.util.ArrayList<>(l.size());
|
java.util.List<Object> out = new java.util.ArrayList<>(l.size());
|
||||||
@ -314,10 +277,10 @@ public class RPushCommand implements Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for {@link #deepMutable(List)}; handles nested lists recursively.
|
* Helper method for {@link #deepMutable(List)}. Recursively processes each element.
|
||||||
*
|
*
|
||||||
* @param v the object to process
|
* @param v The object to process.
|
||||||
* @return a mutable list if input is a list; otherwise, the object itself
|
* @return Mutable list if input is a list, otherwise the value itself.
|
||||||
*/
|
*/
|
||||||
private static Object deepMutableObject(Object v) {
|
private static Object deepMutableObject(Object v) {
|
||||||
if (v instanceof java.util.List<?> l) {
|
if (v instanceof java.util.List<?> l) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user