重构5,无io和无io依赖的模块已经全部重构完毕,接下来是重构剩余的模块,主要是io和依赖io的模块。

This commit is contained in:
fmq
2026-03-22 17:08:40 +08:00
parent 59404207c3
commit 5078d6120e
35 changed files with 11554 additions and 16 deletions
+24
View File
@@ -0,0 +1,24 @@
---
name: fortran-analyzer
description: "分析 Fortran 代码的依赖关系,生成重构优先级列表。触发条件:(1) 用户提到 Fortran 依赖分析/依赖树;(2) 查找重构优先级/从哪里开始重构;(3) 分析函数调用关系;(4) 查看哪些函数依赖其他函数;(5)继续重构任务;(6) 用户问'应该先重构哪个函数'。输出 优先级列表和依赖树。"
---
# Fortran 依赖分析器
分析提取后的 Fortran 文件,生成依赖关系和重构优先级。
## 快速参考
```bash
# 查看下一个优先重构的模块
python3 .claude/skills/fortran-analyzer/scripts/analyze_fortran.py --priority | head -5
# 查看指定函数的依赖树
python3 .claude/skills/fortran-analyzer/scripts/analyze_fortran.py --tree FUNCTION_NAME
```
接下来调用 fortran-to-rust skills 对该模块进行重构。
## 详细参考
不需要看,当--priority | head -1给出的模块含有多个未完成的模块时,说明脚本出错了,应该先修复脚本再继续重构。
- [output_formats.md](references/output_formats.md) - 输出格式说明
- [advanced_usage.md](references/advanced_usage.md) - 高级用法和筛选命令
@@ -0,0 +1,47 @@
# 高级用法
## 重构策略
### 优先级规则
1. **传递未实现=0**: 可立即开始,无依赖
2. **传递未实现=1~3**: 需先完成少数依赖
3. **传递未实现>3**: 依赖链长,最后处理
4. **有IO**: 最后处理(可能需要 I/O 抽象层)
### 筛选命令
```bash
# 最佳候选:无IO + 无未实现依赖
python3 .claude/skills/fortran-analyzer/scripts/analyze_fortran.py --priority | grep "$" | head -10
# 查看特定函数依赖树
python3 .claude/skills/fortran-analyzer/scripts/analyze_fortran.py --tree ALIFR1
# 统计进度
echo "已完成: $(awk -F, '$11=="done"' fortran_analysis.csv | wc -l)"
echo "待处理: $(awk -F, '$11=="pending"' fortran_analysis.csv | wc -l)"
# 生成完整 CSV(含传递依赖)
python3 .claude/skills/fortran-analyzer/scripts/analyze_fortran.py --full > fortran_analysis.csv
```
## SPECIAL_MAPPINGS
一个 Rust 文件实现多个 Fortran 函数时,需更新 `.claude/skills/fortran-analyzer/scripts/analyze_fortran.py`:
```python
SPECIAL_MAPPINGS = {
'gfree': ['gfree0', 'gfreed', 'gfree1'],
'interpolate': ['yint', 'lagran'],
'sgmer': ['sgmer0', 'sgmer1', 'sgmerd'],
# 添加新映射...
}
```
## 脚本内部函数
- `extract_calls()`: 提取 CALL 和 FUNCTION 调用
- `get_transitive_deps()`: 计算传递依赖
- `get_pending_deps()`: 获取未实现依赖
- `print_dependency_tree()`: 打印依赖树
@@ -0,0 +1,49 @@
# 输出格式说明
## 优先级列表 (`--priority`)
```
重构优先级列表 (按未实现依赖排序,无IO优先):
====================================================================================================
单元名 未实现 传递未实现 深度 直接调用 传递调用 IO
----------------------------------------------------------------------------------------------------
ALIFR1 0 0 1 1 1 ○
ALISK1 4 20 5 12 45 ○
```
**列说明:**
| 列 | 含义 |
|----|------|
| 未实现 | 直接依赖中未完成的函数数 |
| 传递未实现 | **关键指标**:所有递归依赖中未完成的函数数 |
| 深度 | 依赖链深度(叶子=0) |
| IO | ○=无IO, ✓=有IO |
**排序规则**: 传递未实现少 → 深度低 → 依赖少
## 依赖树 (`--tree UNIT`)
```
依赖树: ALISK1 ○
============================================================
直接依赖: 4, 传递依赖: 27, 未实现: 20
未实现依赖: ALIFRK, OPACF1, OPADD, ROSSTD, ...
------------------------------------------------------------
○ ALISK1 (4未实现)
├── ○ OPACF1 (6未实现)
│ ├── ○ OPADD (5未实现)
│ │ └── ○ cia_h2h2 (1未实现)
│ └── ✓ locate
└── ○ ALIFRK
```
**符号说明:**
- ✓ = 已完成
- ○ = 待处理
- `(N未实现)` = 该节点有 N 个直接依赖未完成
## CSV 格式
```
fortran_file,unit_name,unit_type,is_pure,common_deps,call_deps,has_io,rust_module,status
```
@@ -0,0 +1,403 @@
---
## TLUSTY208.F 拆分工作 (2026-03-18)
### 任务:将单文件拆分为多个独立模块
**执行流程:**
```bash
cd /home/fmq/program/tlusty/tl208-s54/rust
python3 extract_fortran.py tlusty/tlusty208.f tlusty/extracted/
cp tlusty/*.FOR tlusty/extracted/
```
### 提取结果
| 项目 | 数量 |
|------|------|
| 程序单元 | **304** |
| 子程序 (SUBROUTINE) | 269 |
| 函数 (FUNCTION) | 32 |
| 主程序 (PROGRAM) | 1 |
| BLOCK DATA | **2** (含1个无名) |
| 无 COMMON 依赖的纯函数 | 195 |
> 注意: 2026-03-19 修复了无名 BLOCK DATA 提取问题,单元数从 303 增加到 304
### Include 文件
```
tlusty/extracted/*.FOR
├── IMPLIC.FOR # 隐式类型声明
├── BASICS.FOR # 基本参数和物理常数
├── ITERAT.FOR # 迭代控制参数
├── ALIPAR.FOR # ALI 参数
├── ATOMIC.FOR # 原子数据
├── MODELQ.FOR # 模型物理量
├── ODFPAR.FOR # ODF 参数
└── ARRAY1.FOR # 主工作数组
```
### 编译配置
**关键编译选项**:
```makefile
FFLAGS = -O3 -fno-automatic -mcmodel=large
```
- `-mcmodel=large`: 支持 >2GB 地址空间
- `-fno-automatic`: 所有变量默认为静态存储(兼容旧Fortran代码)
- **不要使用** `-ffixed-line-length-none`: 会将第73-80列的注释区当作代码解析
### 编译验证
```bash
cd tlusty/extracted && make clean && make
# 生成: tlusty_extracted (1,940,488 bytes)
# 直接编译
gfortran -fno-automatic -mcmodel=large -O3 -o tlusty.exe tlusty208.f
# 生成: tlusty.exe (1,997,416 bytes)
```
**结论**: 功能等价,拆分编译文件更小 (-2.9%)
### 拆分编译 vs 直接编译对比
| 方面 | 直接编译 | 拆分编译 |
|------|---------|---------|
| 文件大小 | 1,997,416 B | 1,940,488 B |
| 功能 | 相同 | 相同 |
| 增量编译 | ❌ | ✅ |
| 代码定位 | 困难 | 简单 |
### 无 COMMON 依赖的纯函数 (198个)
可独立测试和重构的单元:
```
ACCEL2, ALIFR1, ALIFR3, ALIFR6, ALIFRK, ALISK1, ALISK2, ALIST1, ALIST2,
ANGSET, BETAH, BHE, BKHSGO, BPOP, BPOPE, BPOPF, BPOPT, BRE, BREZ,
BRTE, BRTEZ, BUTLER, CARBON, CEH12, CHANGE, CHCKSE, CHEAV, CHEAVJ,
CIA_H2H, CIA_H2H2, CIA_H2HE, CIA_HHE, CION, CKOEST, COLH, COLHE,
COLLHE, CONCOR, CORRWM, CROSS, CROSSD, CSPEC, DIELRC, DIETOT, DIVSTR,
DMEVAL, DOPGAM, DWNFR, DWNFR0, DWNFR1, EINT, EMAT, ENTENE, ERFCIN,
ERFCX, EXPINT, EXPINX, EXPO, FFCROS, GAMI, GAMSP, GAULEG, GAUNT,
GETWRD, GFREE0, GFREE1, GFREED, GNTK, GRCOR, GREYD, GRIDP, H2MINUS,
HEPHOT, HIDALG, IJALI2, IJALIS, INCLDY, INDEXX, INIFRS, INILAM, INTERP,
INTHYD, INTLEM, INTXEN, IRC, LAGRAN, LAGUER, LEMINI, LEVGRP, LEVSET,
LEVSOL, LINEQS, LINSEL, LINSET, LINSPL, LOCATE, LTEGR, LUCY, LYMLIN,
MATGEN, MATINV, MEANOP, MEANOPT, MINV3, NEWPOP, NSTOUT, ODF1, ODFFR,
ODFHST, ODFHYD, ODFHYS, ODFMER, OPACFL, OPADD0, OPAHST, OPAINI, OPCTAB,
OSCCOR, OUTPUT, PFCNO, PFFE, PFHEAV, PFNI, PFSPEC, PRCHAN, PRD, PRDINI,
PRINC, PRNT, PROFSP, PSOLVE, PZERT, QUARTC, QUIT, RADPRE, RAPH, RATES1,
RATMAL, RATMAT, RATSP1, RAYINI, RAYSET, RDATAX, READBF, RECHCK, REFLEV,
REIMAN, RHOEOS, RHONEN, ROSSOP, ROSSTD, RTEDF2, RTEFE2, RTESOL, RTE_SC,
SABOLF, SBFCH, SBFHE1, SBFHMI, SBFHMI_OLD, SBFOH, SFFHMI, SFFHMI_ADD,
SGHE12, SGMER0, SGMER1, SGMERD, SIGAVE, SIGK, SIGMAR, SPSIGK, SRTFRQ,
STARK0, STARKA, SWITCH, SZIRC, TDPINI, TIMING, TIOPF, TLUSTY, TRAINI,
TRIDAG, UBETA, VERN16, VERN18, VERN20, VERN26, VERNER, VISINI, VOIGT,
VOIGTE, WN, WNSTOR, XENINI, XK2DOP, YINT, YLINTP, ZMRHO
```
---
## SYNSPEC54.F 拆分工作 (2026-03-18)
### 任务:将单文件拆分为多个独立模块
**执行流程:**
#### 1. 创建提取脚本 `extract_fortran.py`
```python
# 核心功能:
# - 正则匹配 SUBROUTINE/FUNCTION/PROGRAM/BLOCK DATA
# - 查找对应的 END 语句确定边界
# - 提取到独立 .f 文件
# - 分析 COMMON 块依赖
# - 生成 Makefile
```
#### 2. 运行提取
```bash
cd /home/fmq/program/tlusty/tl208-s54/rust
python3 extract_fortran.py synspec/synspec54.f synspec/extracted/
cp synspec/*.FOR synspec/extracted/
```
#### 3. 提取结果
| 项目 | 数量 |
|------|------|
| 程序单元 | 168 |
| 子程序 (SUBROUTINE) | 134 |
| 函数 (FUNCTION) | 33 |
| 主程序 (PROGRAM) | 1 |
| 总代码行数 | 23,050 |
| 无 COMMON 依赖的纯函数 | 93 |
#### 4. 编译配置
**关键编译选项** (解决大型 COMMON 数组链接问题):
```makefile
FFLAGS = -O3 -fno-automatic -mcmodel=large
```
- `-mcmodel=large`: 支持 >2GB 地址空间
- `-fno-automatic`: 所有变量默认为静态存储(兼容旧Fortran代码)
- **不要使用** `-ffixed-line-length-none`: 会将第73-80列的注释区当作代码解析
#### 5. 编译验证
```bash
cd synspec/extracted && make clean && make
# 生成: synspec_extracted (1,000,408 bytes)
```
**对比原始编译:**
```bash
gfortran -O3 -fno-automatic -mcmodel=large -o synspec_direct.exe synspec54.f
# 生成: synspec_direct.exe (1,044,928 bytes)
```
**结论**: 功能完全等价,拆分编译更小 (-4.3%)
### 生成的文件结构
```
synspec/extracted/
├── synspec.f # 主程序 (174行)
├── start.f # 子程序 (107行)
├── sbfhmi.f # H⁻ 光电离截面函数 (42行)
├── expint.f # 指数积分函数 (18行)
├── ... # 共168个 .f 文件
├── PARAMS.FOR # 参数定义 (include)
├── MODELP.FOR # 模型参数 (include)
├── LINDAT.FOR # 谱线数据 (include)
├── SYNTHP.FOR # 合成谱参数 (include)
├── WINCOM.FOR # 窗口通信 (include)
├── Makefile # 自动构建
├── _SUMMARY.txt # 提取摘要
├── _COMMON_ANALYSIS.txt # COMMON 依赖分析
└── _PURE_UNITS.txt # 纯函数列表
```
### COMMON 块分析结果
**有 COMMON 依赖的单元**: 75 个
**唯一 COMMON 块**: 68 个
主要 COMMON 块:
- `BLAPAR`, `LIMPAR` - 谱线参数
- `EMFLUX` - 辐射流
- `RTEOPA` - 辐射转移不透明度
- `NLTPOP` - 非LTE布居数
- `lasers` - 激光数据处理
### 拆分编译 vs 直接编译对比
| 方面 | 直接编译 | 拆分编译 |
|------|---------|---------|
| 文件大小 | 1,044,928 B | 1,000,408 B |
| 功能 | 相同 | 相同 |
| 增量编译 | ❌ | ✅ |
| 代码定位 | 困难 | 简单 |
| 模块化重构 | 困难 | 容易 |
### 提取脚本位置
```
extract_fortran.py
```
### 推荐用法
```bash
# 开发/调试/重构
cd synspec/extracted && make
# 生产环境/快速编译
cd synspec && gfortran -O3 -fno-automatic -mcmodel=large -o synspec.exe synspec54.f
```
### 无 COMMON 依赖的纯函数 (93个)
可独立测试和重构的函数:
```
CARBON, CHANGE, CHCKAB, CIA_H2H, CIA_H2H2, CIA_H2HE, CIA_HHE,
COUNT_WORDS, DENSIT, DIVHE2, DIVSTR, DWNFR0, DWNFR1, EPS,
EXOPF, EXPINT, EXTPRF, FEAUTR, GAMHE, GAUNT, GETWRD, GFREE,
GNTK, GRIEM, H2MINUS, H2OPF, HE2SET, HE2SEW, HEPHOT, HESET,
HIDALG, HYDINI, HYDTAB, HYLSET, HYLSEW, INIBLM, INKUR, INPBF,
INTERP, INTHYD, INTRP, INTXEN, IRWPF, ISPEC, LEVSOL, LINEQS,
LOCATE, LYMLIN, MATINV, MOLOP, MPARTF, OPADD, PARTDV, PARTF,
PFFE, PFHEAV, PFNI, PFSPEC, PHTX, QUIT, RATMAT, READBF,
REIMAN, SABOLF, SBFCH, SBFHE1, SBFHMI, SBFHMI_OLD, SBFOH,
SETRAY, SFFHMI, SFFHMI_OLD, SGHE12, SGMERG, SPSIGK, STARK0,
STARKA, STARKIR, STATE0, SYNSPEC, TINT, TRIDAG, VELSET,
VOIGTE, VOPF, WGTJH1, WN, WNSTOR, WTOT, XENINI, XK2DOP, YINT, YLINTP
```
---
## 功能验证测试 (2026-03-19)
### 测试环境变量
```bash
export TL208=/home/fmq/program/tlusty
export TLUSTY=$TL208/tl208-s54
export LINELIST=$TL208/linelist
export IRON=$TL208/irondata
export OPTABLES=$TL208/optables
```
### 测试目录
```
tests/tlusty/hhe/ # H-He 模型测试用例
├── hhe35lt.5 # LTE 模型输入
├── hhe35nc.5 # NLTE continua 模型输入
├── hhe35nl.5 # NLTE with lines 模型输入
└── hhe35*.7,9,14 # 预期输出文件
```
### 测试流程
```bash
cd /home/fmq/program/tlusty/tl208-s54/rust/tests/tlusty
# 创建测试目录
mkdir -p test_extracted
cd test_extracted
cp ../hhe/*.5 .
ln -sf $TLUSTY/data data
# 设置环境变量
export TL208=/home/fmq/program/tlusty
export TLUSTY=$TL208/tl208-s54
export LINELIST=$TL208/linelist
export IRON=$TL208/irondata
export OPTABLES=$TL208/optables
# 可执行文件路径
EXE=../../tlusty/extracted/build/tlusty_extracted
# 测试1: LTE 模型 (从零开始)
$EXE < hhe35lt.5 > hhe35lt.6
cp fort.7 hhe35lt.7; cp fort.9 hhe35lt.9; cp fort.14 hhe35lt.14
# 测试2: NLTE continua (使用 LTE 作为初始模型)
cp hhe35lt.7 fort.8
$EXE < hhe35nc.5 > hhe35nc.6
cp fort.7 hhe35nc.7; cp fort.9 hhe35nc.9; cp fort.14 hhe35nc.14
# 测试3: NLTE with lines (使用 NC 作为初始模型)
cp hhe35nc.7 fort.8
$EXE < hhe35nl.5 > hhe35nl.6
cp fort.7 hhe35nl.7; cp fort.9 hhe35nl.9; cp fort.14 hhe35nl.14
# 验证: 与原始结果比较
diff hhe35lt.7 ../hhe/hhe35lt.7
diff hhe35nc.7 ../hhe/hhe35nc.7
diff hhe35nl.7 ../hhe/hhe35nl.7
```
### 测试结果
| 测试用例 | 拆分编译 | 直接编译 | 原始结果 |
|----------|----------|----------|----------|
| hhe35lt (LTE) | ✓ 通过 | ✓ 通过 | ✓ 相同 |
| hhe35nc (NLTE continua) | ✓ 通过 | ✓ 通过 | ✓ 相同 |
| hhe35nl (NLTE lines) | ✓ 通过 | ✓ 通过 | ✓ 相同 |
**MD5 校验和 (hhe35nl.7)**:
```
01f3169947ca24bf1c989619b83ae8f2
```
**结论**: 拆分编译与直接编译输出完全相同,功能验证通过
---
## SYNSPEC54 功能验证测试 (2026-03-19)
### 测试目录
```
tests/synspec/hhe/
├── hhe35nl.5 # 输入文件
├── hhe35nl.7 # 模型大气 (来自 TLUSTY 测试)
├── fort.55.con # 附加输入文件
├── data # 数据目录路径文件 (内容: $TLUSTY/data)
└── results/ # 预期输出
├── hhe35nl.spec # 合成光谱
├── hhe35nl.cont # 连续谱
└── hhe35nl.iden # 谱线标识
```
### 测试流程
```bash
cd /home/fmq/program/tlusty/tl208-s54/rust/tests/synspec/hhe
# 设置环境变量
export TL208=/home/fmq/program/tlusty
export TLUSTY=$TL208/tl208-s54
export LINELIST=$TL208/linelist
export IRON=$TL208/irondata
export OPTABLES=$TL208/optables
# 准备输入文件
cp hhe35nl.7 fort.8
ln -sf fort.55.con fort.55
ln -sf $TLUSTY/data/gfATO.dat fort.19
# 关键: data 必须是符号链接指向数据目录
rm -f data
ln -sf $TLUSTY/data data
# 可执行文件路径
EXE_ORIG=$TLUSTY/synspec/synspec.exe
EXE_DIRECT=../../synspec/synspec_direct.exe
EXE_EXTRACTED=../../synspec/extracted/build/synspec_extracted
# 测试原始版本
$EXE_ORIG < hhe35nl.5 > hhe35nl_orig.log
cp fort.7 hhe35nl_orig.spec; cp fort.17 hhe35nl_orig.cont
# 测试直接编译版本
rm -f fort.7 fort.17 fort.12
$EXE_DIRECT < hhe35nl.5 > hhe35nl_direct.log
cp fort.7 hhe35nl_direct.spec; cp fort.17 hhe35nl_direct.cont
# 测试拆分编译版本
rm -f fort.7 fort.17 fort.12
$EXE_EXTRACTED < hhe35nl.5 > hhe35nl_extracted.log
cp fort.7 hhe35nl_extracted.spec; cp fort.17 hhe35nl_extracted.cont
# 验证
diff hhe35nl_orig.spec hhe35nl_direct.spec
diff hhe35nl_orig.spec hhe35nl_extracted.spec
# 恢复 data 文件
rm -f data
echo "/home/fmq/program/tlusty/tl208-s54/data" > data
```
### 测试结果
| 测试用例 | 原始程序 | 直接编译 | 拆分编译 |
|----------|----------|----------|----------|
| hhe35nl (NLTE lines) | ✓ 通过 | ✓ 通过 | ✓ 相同 |
**MD5 校验和 (hhe35nl.spec)**:
```
7925533b21b16d6bcdfff40e626cab83
```
**注意事项**:
- `data` 文件/符号链接必须正确设置,否则报错 `Cannot open file './data/h1.dat': Not a directory`
- 拆分编译程序与原始程序输出完全相同,功能验证通过
@@ -0,0 +1,438 @@
#!/usr/bin/env python3
"""
分析 TLUSTY Fortran 文件,提取函数依赖信息。
用法:
python3 analyze_fortran.py # 输出 CSV(带完整依赖)
python3 analyze_fortran.py --tree # 输出依赖树(文本格式)
python3 analyze_fortran.py --priority # 输出重构优先级列表
"""
import os
import re
import glob
import argparse
from collections import defaultdict
def extract_includes(content):
"""提取 INCLUDE 文件列表"""
includes = re.findall(r"INCLUDE\s*'([^']+)\.FOR'", content, re.IGNORECASE)
return [inc for inc in includes if inc.upper() != 'IMPLIC']
def extract_commons(content):
"""提取 COMMON 块名称"""
# 匹配 COMMON/NAME/ 或 common/name/
commons = re.findall(r'(?i)^\s*COMMON\s*/(\w+)/', content, re.MULTILINE)
return list(set(commons))
# Fortran 内置函数列表(不需要追踪)
FORTRAN_INTRINSICS = {
'SIN', 'COS', 'TAN', 'ASIN', 'ACOS', 'ATAN', 'ATAN2',
'SINH', 'COSH', 'TANH',
'EXP', 'LOG', 'LOG10', 'LOG2',
'SQRT', 'ABS', 'MOD', 'SIGN',
'MAX', 'MIN', 'MAX0', 'MIN0', 'MAX1', 'MIN1', 'AMAX0', 'AMIN0',
'INT', 'IFIX', 'IDINT', 'FLOAT', 'SNGL', 'DBLE', 'CMPLX',
'REAL', 'AIMAG', 'CONJG',
'ICHAR', 'CHAR', 'INDEX', 'LEN', 'LGE', 'LGT', 'LLE', 'LLT',
'DOT_PRODUCT', 'MATMUL', 'TRANSPOSE', 'RESHAPE',
'SIZE', 'SHAPE', 'LBOUND', 'UBOUND',
'ALLOCATED', 'ALLOCATE', 'DEALLOCATE',
'KIND', 'SELECTED_REAL_KIND', 'SELECTED_INT_KIND',
'DIGITS', 'EPSILON', 'HUGE', 'TINY', 'PRECISION', 'RANGE',
'FLOOR', 'CEILING', 'NINT', 'ANINT',
'ADJUSTL', 'ADJUSTR', 'TRIM', 'REPEAT', 'SCAN', 'VERIFY',
'PRESENT', 'ASSOCIATED',
# TLUSTY 常用数学函数
'ERF', 'ERFC', 'GAMMA', 'LOG_GAMMA',
}
def extract_calls(content, known_functions=None):
"""提取 CALL 语句和 FUNCTION 调用
Args:
content: Fortran 源码
known_functions: 已知的函数名集合(用于区分函数调用和数组访问)
"""
calls = set()
# 1. 提取 CALL 语句(支持有括号和无括号两种形式)
# CALL NAME(...) 或 CALL NAME
call_stmts = re.findall(r'(?i)CALL\s+(\w+)(?:\s*\(|\s*$|\s*\n)', content)
calls.update(c.upper() for c in call_stmts)
# 2. 提取可能的 FUNCTION 调用
if known_functions:
# 只匹配已知函数名
func_assign = re.findall(r'(?i)=\s*([A-Z][A-Z0-9]*)\s*\(', content)
calls.update(f.upper() for f in func_assign
if f.upper() in known_functions and f.upper() not in FORTRAN_INTRINSICS)
func_expr = re.findall(r'(?i)[=(,]\s*([A-Z][A-Z0-9]*)\s*\(', content)
calls.update(f.upper() for f in func_expr
if f.upper() in known_functions and f.upper() not in FORTRAN_INTRINSICS)
return list(calls)
def has_file_io(content):
"""检查是否有文件 I/O"""
patterns = [
r'OPEN\s*\(',
r'READ\s*\(\s*\d+',
r'WRITE\s*\(\s*\d+',
r'write\s*\(',
r'read\s*\(',
]
for p in patterns:
if re.search(p, content, re.IGNORECASE):
return True
return False
def extract_unit_info(content, filename):
"""提取单元信息"""
units = []
# 匹配 SUBROUTINE
sub_match = re.search(r'(?i)^\s*SUBROUTINE\s+(\w+)', content, re.MULTILINE)
if sub_match:
units.append(('SUBROUTINE', sub_match.group(1).upper()))
# 匹配 FUNCTION
func_match = re.search(r'(?i)^\s*(?:REAL(?:\*\d+)?|INTEGER(?:\*\d+)?|DOUBLE\s*PRECISION)?\s*FUNCTION\s+(\w+)', content, re.MULTILINE)
if func_match:
units.append(('FUNCTION', func_match.group(1).upper()))
# 匹配 BLOCK DATA(注意:名字必须在同一行,不能跨行匹配注释 C)
# Fortran 中 C 开头的行是注释,不应被匹配为名字
block_match = re.search(r'(?i)^\s*BLOCK\s*DATA\s*(\w+)?\s*$', content, re.MULTILINE)
if block_match:
name = block_match.group(1).upper() if block_match.group(1) else '_UNNAMED_'
units.append(('BLOCK DATA', name))
# 如果都没匹配到,使用文件名
if not units:
base = os.path.splitext(filename)[0]
units.append(('UNKNOWN', base.upper()))
return units
# 特殊映射:一个 Rust 文件实现多个 Fortran 函数
SPECIAL_MAPPINGS = {
# Rust 文件名 -> [Fortran 函数名列表]
'gfree': ['gfree0', 'gfreed', 'gfree1'],
'interpolate': ['yint', 'lagran'],
'sgmer': ['sgmer0', 'sgmer1', 'sgmerd'],
'ctdata': ['hction', 'hctrecom'],
'cross': ['cross', 'crossd'],
'expint': ['eint', 'expinx'],
'erfcx': ['erfcx', 'erfcin'],
'lineqs': ['lineqs', 'lineqs_nr'],
'gamsp': ['gamsp'], # alias
'bhe': ['bhe', 'bhed', 'bhez'], # 流体静力学平衡方程
'comset': ['comset'], # Compton 散射参数设置
'ghydop': ['ghydop'], # 氢不透明度 (Gomez 表)
'levgrp': ['levgrp'], # 能级分组
'profil': ['profil'], # 标准吸收轮廓
'linspl': ['linspl'], # 谱线轮廓设置
}
def find_rust_module(fortran_name, rust_dir):
"""查找对应的 Rust 模块"""
# 先检查直接匹配
rust_file = os.path.join(rust_dir, f"{fortran_name}.rs")
if os.path.exists(rust_file):
return f"src/math/{fortran_name}.rs"
# 检查特殊映射
for rust_mod, fortran_funcs in SPECIAL_MAPPINGS.items():
if fortran_name in fortran_funcs:
return f"src/math/{rust_mod}.rs"
return ""
def get_transitive_deps(unit_name, units_dict, visited=None):
"""递归获取所有传递调用依赖"""
if visited is None:
visited = set()
if unit_name in visited:
return set()
visited.add(unit_name)
if unit_name not in units_dict:
return set()
direct_calls = units_dict[unit_name].get('call_deps', [])
all_deps = set(direct_calls)
for dep in direct_calls:
all_deps.update(get_transitive_deps(dep, units_dict, visited.copy()))
return all_deps
def get_pending_deps(unit_name, units_dict, visited=None):
"""获取尚未实现的直接依赖"""
if unit_name not in units_dict:
return []
calls = units_dict[unit_name].get('call_deps', [])
pending = [d for d in calls if d not in units_dict or units_dict[d].get('status') != 'done']
return pending
def get_transitive_pending_deps(unit_name, units_dict, visited=None):
"""递归获取所有传递的未实现依赖"""
if visited is None:
visited = set()
if unit_name in visited:
return set()
visited.add(unit_name)
if unit_name not in units_dict:
return set()
direct_calls = units_dict[unit_name].get('call_deps', [])
# 未实现的直接依赖
pending_deps = set(d for d in direct_calls if d not in units_dict or units_dict[d].get('status') != 'done')
# 递归获取所有依赖的未实现依赖
for dep in direct_calls:
pending_deps.update(get_transitive_pending_deps(dep, units_dict, visited.copy()))
return pending_deps
def get_transitive_commons(unit_name, units_dict, visited=None):
"""递归获取所有传递 COMMON 依赖"""
if visited is None:
visited = set()
if unit_name in visited:
return set()
visited.add(unit_name)
if unit_name not in units_dict:
return set()
direct_commons = set(units_dict[unit_name].get('common_deps', []))
direct_calls = units_dict[unit_name].get('call_deps', [])
all_commons = direct_commons.copy()
for dep in direct_calls:
all_commons.update(get_transitive_commons(dep, units_dict, visited.copy()))
return all_commons
def calculate_depth(unit_name, units_dict, memo=None):
"""计算依赖深度(叶子节点深度为0"""
if memo is None:
memo = {}
if unit_name in memo:
return memo[unit_name]
if unit_name not in units_dict:
return 0
calls = units_dict[unit_name].get('call_deps', [])
if not calls:
memo[unit_name] = 0
return 0
max_dep_depth = 0
for dep in calls:
if dep != unit_name: # 避免自引用
max_dep_depth = max(max_dep_depth, calculate_depth(dep, units_dict, memo))
depth = max_dep_depth + 1
memo[unit_name] = depth
return depth
def print_dependency_tree(unit_name, units_dict, indent=0, visited=None, prefix="", show_pending_count=True):
"""打印依赖树(文本格式)"""
if visited is None:
visited = set()
if unit_name in visited:
print(f"{prefix}[循环引用: {unit_name}]")
return
visited.add(unit_name)
if unit_name not in units_dict:
print(f"{prefix}{unit_name} [未找到/未实现]")
return
unit = units_dict[unit_name]
status = unit.get('status', 'pending')
status_mark = "" if status == "done" else ""
# 计算未实现依赖数
pending_count = len(get_pending_deps(unit_name, units_dict))
pending_str = f" ({pending_count}未实现)" if show_pending_count and pending_count > 0 else ""
print(f"{prefix}{status_mark} {unit_name}{pending_str}")
calls = unit.get('call_deps', [])
# 按未实现依赖数排序(未实现多的在前,因为更紧迫)
pending_sorted = sorted(calls, key=lambda d: -len(get_pending_deps(d, units_dict) if d in units_dict else []))
for i, dep in enumerate(pending_sorted):
is_last = (i == len(pending_sorted) - 1)
connector = "└── " if is_last else "├── "
print_dependency_tree(dep, units_dict, indent + 1, visited.copy(), prefix + connector, show_pending_count)
def main():
parser = argparse.ArgumentParser(description='分析 TLUSTY Fortran 文件依赖')
parser.add_argument('--tree', metavar='UNIT', help='输出指定单元的依赖树')
parser.add_argument('--priority', action='store_true', help='输出重构优先级列表')
parser.add_argument('--full', action='store_true', help='输出完整传递依赖')
args = parser.parse_args()
extracted_dir = "/home/fmq/program/tlusty/tl208-s54/rust/tlusty/extracted"
rust_dir = "/home/fmq/program/tlusty/tl208-s54/rust/src/math"
# 第一遍:收集所有已定义的 SUBROUTINE 和 FUNCTION 名称
all_defined_units = set()
fortran_files = sorted(glob.glob(os.path.join(extracted_dir, "*.f")))
for fpath in fortran_files:
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
units = extract_unit_info(content, os.path.basename(fpath))
for unit_type, unit_name in units:
all_defined_units.add(unit_name)
# 第二遍:收集所有单元信息(使用已知函数名来过滤调用)
units_dict = {}
for fpath in fortran_files:
fname = os.path.basename(fpath)
base_name = os.path.splitext(fname)[0]
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
includes = extract_includes(content)
commons = extract_commons(content)
calls = extract_calls(content, known_functions=all_defined_units)
io = has_file_io(content)
units = extract_unit_info(content, fname)
is_pure = len(includes) <= 1 and len(commons) == 0 and not io
rust_mod = find_rust_module(base_name, rust_dir)
status = "done" if rust_mod else "pending"
for unit_type, unit_name in units:
units_dict[unit_name] = {
'fortran_file': fname,
'unit_type': unit_type,
'is_pure': is_pure,
'common_deps': includes + commons,
'call_deps': calls,
'has_io': io,
'rust_module': rust_mod,
'status': status,
}
# --tree 模式:输出依赖树
if args.tree:
unit_name = args.tree.upper()
if unit_name in units_dict:
unit = units_dict[unit_name]
trans_pending = get_transitive_pending_deps(unit_name, units_dict)
trans_calls = get_transitive_deps(unit_name, units_dict)
status_mark = "" if unit['status'] == "done" else ""
print(f"依赖树: {unit_name} {status_mark}")
print("=" * 60)
print(f"直接依赖: {len(unit['call_deps'])}, 传递依赖: {len(trans_calls)}, "
f"未实现: {len(trans_pending)}")
if trans_pending:
print(f"未实现依赖: {', '.join(sorted(trans_pending)[:10])}")
if len(trans_pending) > 10:
print(f" ... 还有 {len(trans_pending) - 10}")
print("-" * 60)
print_dependency_tree(unit_name, units_dict)
else:
print(f"未找到单元: {unit_name}")
# 尝试模糊匹配
matches = [u for u in units_dict if args.tree.lower() in u.lower()]
if matches:
print(f"可能的匹配: {', '.join(matches[:10])}")
return
# --priority 模式:输出重构优先级
if args.priority:
# 计算每个单元的依赖深度和传递依赖数
priority_list = []
memo = {}
for unit_name, unit in units_dict.items():
if unit['status'] == 'done':
continue
# 跳过无法识别程序单元的文件(如纯注释文件)
if unit['unit_type'] == 'UNKNOWN':
continue
# 跳过 BLOCK DATA(数据初始化块,不是函数)
if unit['unit_type'] == 'BLOCK DATA':
continue
depth = calculate_depth(unit_name, units_dict, memo)
trans_calls = len(get_transitive_deps(unit_name, units_dict))
trans_commons = len(get_transitive_commons(unit_name, units_dict))
pending_deps = len(get_pending_deps(unit_name, units_dict))
trans_pending = len(get_transitive_pending_deps(unit_name, units_dict))
priority_list.append({
'name': unit_name,
'depth': depth,
'direct_calls': len(unit['call_deps']),
'trans_calls': trans_calls,
'direct_commons': len(unit['common_deps']),
'trans_commons': trans_commons,
'pending_deps': pending_deps,
'trans_pending': trans_pending,
'has_io': unit['has_io'],
'is_pure': unit['is_pure'],
})
# 按优先级排序:未实现依赖少 > 无IO > 深度低
priority_list.sort(key=lambda x: (x['trans_pending'], x['has_io'], x['depth'], x['trans_calls']))
print("重构优先级列表 (按未实现依赖排序,同数量优先无IO)")
print("=" * 100)
print(f"{'单元名':<20} {'未实现':>6} {'传递未实现':>10} {'深度':>4} {'直接调用':>8} {'传递调用':>8} {'IO':>4}")
print("-" * 100)
for item in priority_list[:100]: # 显示前100个
io_mark = "" if item['has_io'] else ""
print(f"{item['name']:<20} {item['pending_deps']:>6} {item['trans_pending']:>10} "
f"{item['depth']:>4} {item['direct_calls']:>8} {item['trans_calls']:>8} {io_mark:>4}")
return
# 默认模式:输出 CSV(带完整依赖)
if args.full:
print("fortran_file,unit_name,unit_type,is_pure,common_deps,call_deps,"
"trans_commons,trans_calls,has_io,rust_module,status")
else:
print("fortran_file,unit_name,unit_type,is_pure,common_deps,call_deps,has_io,rust_module,status")
memo = {}
for unit_name, unit in units_dict.items():
if args.full:
trans_commons = get_transitive_commons(unit_name, units_dict)
trans_calls = get_transitive_deps(unit_name, units_dict)
print(f"{unit['fortran_file']},{unit_name},{unit['unit_type']},{unit['is_pure']},"
f"\"{'|'.join(unit['common_deps'])}\",\"{'|'.join(unit['call_deps'])}\","
f"\"{'|'.join(trans_commons)}\",\"{'|'.join(trans_calls)}\","
f"{unit['has_io']},{unit['rust_module']},{unit['status']}")
else:
print(f"{unit['fortran_file']},{unit_name},{unit['unit_type']},{unit['is_pure']},"
f"\"{'|'.join(unit['common_deps'])}\",\"{'|'.join(unit['call_deps'])}\","
f"{unit['has_io']},{unit['rust_module']},{unit['status']}")
if __name__ == "__main__":
main()