fix: 構造検知の根本的改善 — 変数名に依存しないマッチング検出

COBOL技術者による徹底検証で発見された根本問題と修正:

問題1: 構造検知の信号が変数名の命名規則に依存しすぎていた
- EOF 固定 → WS-E1/WS-END-1/FE-1 も検知
- INTO ありのみ → READ AT END のみも検知
- IF 比較が WS- またはハイフン必須 → どんな名前でも検知
- OPEN 1行複数ファイルのみ → 複数行も検知

問題2: mn_output_mode が2ファイル4分岐でも M:N と誤判定
- しきい値を select>=3 or (select>=2 and 分岐>=4) に引き上げ
- 標準的な2ファイルマッチングプログラムを誤判定しない

問題3: has_cross_file_cmp が欠落していた
- ルールエンジンに IF K1 = K2 のような比較情報を注入
- 数字リテラルとの比較は除外(IF WS-COUNT > 0 など)

効果: 6種類の異なるコーディングスタイルすべてが一貫してマッチング判定
回帰: 767 passed (0 new)
This commit is contained in:
NB-076
2026-06-21 16:27:17 +08:00
parent 4be2aae66d
commit 875c593d85
3 changed files with 57 additions and 22 deletions
+39 -19
View File
@@ -101,33 +101,53 @@ def _detect_matching_structure(source_upper: str) -> float:
返回确信度 0.0~0.55,0.0 表示不是匹配。
匹配程序的结构性特征:
信号 1: READ + AT END + EOF(文件读取循环)
信号 2: PERFORM UNTIL + EOF(主循环)
信号 1: READ + AT END + EOF/WS-*E* 变量(文件读取循环)
信号 2: PERFORM UNTIL + EOF/WS-*E* 变量(主循环)
信号 3: ELSE 体内 READ(条件性读取——匹配核心)
信号 4: IF 比较两个连字号字段(跨文件字段比较)
信号 4: IF 比较两个字段(跨文件字段比较,任何命名风格
信号 5: 2+ 文件 OPEN INPUT(多文件输入)
"""
import re
signals = 0
# 信号 1: READ + AT END + EOF(文件读取循环)
if re.search(r'READ\s+\w+.*AT\s+END.*EOF', source_upper):
signals += 1
# 信号 2: PERFORM UNTIL + EOF(主循环)
if re.search(r'PERFORM\s+UNTIL\s+.*EOF', source_upper):
signals += 1
# 信号 3: ELSE 体内 READ(条件性读取)
if re.search(r'ELSE\s+.*READ\s+', source_upper):
signals += 1
# 信号 4: IF 比较两个字段(跨文件字段比较,可有/无连字号)
if (re.search(r'IF\s+\w+-\w+\s*[=<>]\s*\w+-\w+', source_upper) # 标准命名 CUST-CODE
or re.search(r'IF\s+WS\w+\s*[=<>]\s+WS\w+', source_upper)): # 无连字符 WSKEY1
signals += 1
# 信号 5: 2+ 文件 OPEN INPUT
if re.search(r'OPEN\s+INPUT\s+\w+\s+\w+', source_upper):
# 信号 1: READ + AT END + 赋值(任何命名风格的 EOF 标志)
# COBOL 匹配程序至少有一个 READ ... AT END MOVE ...
# 匹配: READ F1 AT END MOVE 'Y' TO WS-EOF-A.
# 匹配: READ F1 INTO R1 AT END MOVE 'Y' TO WS-END-1.
# 匹配: READ F1 AT END MOVE 'Y' TO FE-1.
if re.search(r'READ\s+\w+(?:\s+INTO\s+\w+)?\s+AT\s+END', source_upper):
signals += 1
# 确信度: 5 中 5 = 0.55, 5 中 4 = 0.50, 5 中 3 = 0.40
# 信号 1b: 第二个 READ(匹配程序通常有 2 个 READ)
reads = re.findall(r'\bREAD\s+\w+(?:\s+INTO\s+\w+)?', source_upper)
if len(reads) >= 2:
signals += 1
# 信号 2: PERFORM UNTIL + 结束条件(EOF, E1, END-FLAG 等)
if re.search(r'PERFORM\s+UNTIL\s+\w+[-A-Z0-9]*\s*=\s*[\'\"][YN]', source_upper):
signals += 1
# 信号 2b: GO TO 循环(LOOP〜EXIT-PGM/END
if (re.search(r'GO\s+TO\s+LOOP|GO\s+TO\s+[A-Z]*-L|[A-Z]*LP\b', source_upper) and
re.search(r'IF\s+\w+.*=\s*[\'\"][YN]', source_upper)):
signals += 1
# 信号 3: ELSE 体内 READ(条件性读取——匹配核心)
if re.search(r'ELSE\s+.*READ\s+', source_upper) or re.search(r'ELSE\s+\w+\s+READ\s+', source_upper):
signals += 1
# 信号 4: IF 比较两个不同变量(跨文件字段比较,任何命名风格)
# K1 = K2 (简单名), CUST-CODE = ORDR-CODE (连字号), WS-KEY1 = WS-KEY2
if re.search(r'IF\s+\w[\w-]*\s*[=<>]\s*\w[\w-]*', source_upper):
signals += 1
# 信号 5: 2+ 文件 OPEN INPUT
if (re.search(r'OPEN\s+INPUT\s+\w+\s+\w+', source_upper) or # 同一行
re.search(r'OPEN\s+INPUT\s+\w+[.\s].*OPEN\s+INPUT', source_upper)): # 别行
signals += 1
# 确信度: 6 中 5+ = 0.55, 4 = 0.50, 3 = 0.40
if signals >= 5:
return 0.55
elif signals >= 4: