Files
cobol-java-v3/test-data/test_hina_high_density.py
T
NB-076 50995d3335 chore: SETUP.md + 测试报告脚本 + 文档更新
- SETUP.md: 完整环境搭建指南(同事用)
- SETUP_QUICK.md: 快速搭环境(4步)
- s22~s26: TNA端到端、覆盖率报告、回归检查
- procedure_grammar.lark: 实验性Lark语法

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 08:50:17 +08:00

386 lines
18 KiB
Python

"""
HINA 全35类型 高密度测试 — 每种类型 5+ 变体
包括: 正常形 / 別スタイル / 最小形 / 境界形 / FP攻撃 / FN攻撃 / 命名バリエーション
"""
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from hina.pipeline import classify_program
from hina.classifier import detect_keyword
STATS = {"pass": 0, "fail": 0, "total": 0, "by_type": {}}
def test(hina_id, variant, name, src, check_matching=None, check_category=None):
STATS["total"] += 1
STATS["by_type"].setdefault(hina_id, {"pass": 0, "fail": 0, "total": 0})
STATS["by_type"][hina_id]["total"] += 1
try:
c = classify_program(src)
kw = detect_keyword(src)
except Exception as e:
print(f'CRASH {hina_id}/{variant} {name[:25]:25s} {str(e)[:50]}')
STATS["fail"] += 1
STATS["by_type"][hina_id]["fail"] += 1
return
cat = c['category']
conf = c['confidence']
is_match = 'マッチング' in cat or '二段階' in cat
issues = []
if check_matching is True and not is_match:
issues.append(f'want MATCH got {cat}')
elif check_matching is False and is_match:
issues.append(f'want NON-MATCH got {cat}')
if check_category and cat != check_category:
issues.append(f'want {check_category} got {cat}')
if issues:
print(f'FAIL {hina_id}/{variant} {name[:25]:25s} {cat:20s} {conf:.2f} | {issues[0]}')
STATS["fail"] += 1
STATS["by_type"][hina_id]["fail"] += 1
else:
STATS["pass"] += 1
STATS["by_type"][hina_id]["pass"] += 1
P = ' IDENTIFICATION DIVISION. PROGRAM-ID. T. DATA DIVISION. WORKING-STORAGE SECTION. '
print('='*95)
print('HINA 35 TYPES HIGH-DENSITY TEST')
print('='*95)
# ════════════════════════════════════
# MATCHING SERIES
# ════════════════════════════════════
print('\n--- MATCHING ---')
test('M','1to1','std WS-KEY',P+'''
01 WS-KEY-A PIC X(10). 01 WS-KEY-B PIC X(10).
01 WS-EOF-A PIC X VALUE 'N'. 01 WS-EOF-B PIC X VALUE 'N'.
PROCEDURE DIVISION. OPEN INPUT F1 F2.
READ F1 AT END MOVE 'Y' TO WS-EOF-A. READ F2 AT END MOVE 'Y' TO WS-EOF-B.
PERFORM UNTIL WS-EOF-A='Y' OR WS-EOF-B='Y'
IF WS-KEY-A=WS-KEY-B DISPLAY 'M' READ F1 AT END MOVE 'Y' TO WS-EOF-A READ F2 AT END MOVE 'Y' TO WS-EOF-B
ELSE IF WS-KEY-A<WS-KEY-B READ F1 AT END MOVE 'Y' TO WS-EOF-A
ELSE READ F2 AT END MOVE 'Y' TO WS-EOF-B END-IF
END-PERFORM. CLOSE F1 F2. STOP RUN.''',check_matching=True)
test('M','goto','GO TO style',P+'''
01 WS-KEY-A PIC X(10). 01 WS-KEY-B PIC X(10).
01 WS-E1 PIC X VALUE 'N'. 01 WS-E2 PIC X VALUE 'N'.
PROCEDURE DIVISION. OPEN INPUT F1 F2.
READ F1 AT END MOVE 'Y' TO WS-E1. READ F2 AT END MOVE 'Y' TO WS-E2.
LP.IF WS-E1='Y' OR WS-E2='Y' GO TO EP.
IF WS-KEY-A=WS-KEY-B DISPLAY 'M' READ F1 AT END MOVE 'Y' TO WS-E1 READ F2 AT END MOVE 'Y' TO WS-E2
ELSE IF WS-KEY-A<WS-KEY-B READ F1 AT END MOVE 'Y' TO WS-E1
ELSE READ F2 AT END MOVE 'Y' TO WS-E2. GO TO LP.
EP.CLOSE F1 F2. STOP RUN.''',check_matching=True)
test('M','short','short names',P+'''
01 A PIC X(10). 01 B PIC X(10). 01 C PIC X VALUE 'N'. 01 D PIC X VALUE 'N'.
PROCEDURE DIVISION. OPEN INPUT F1 F2.
READ F1 AT END MOVE 'Y' TO C. READ F2 AT END MOVE 'Y' TO D.
PERFORM UNTIL C='Y' OR D='Y'
IF A=B DISPLAY 'M' READ F1 AT END MOVE 'Y' TO C READ F2 AT END MOVE 'Y' TO D
ELSE IF A<B READ F1 AT END MOVE 'Y' TO C ELSE READ F2 AT END MOVE 'Y' TO D END-IF
END-PERFORM. CLOSE F1 F2. STOP RUN.''',check_matching=True)
test('M','hyphen','CUST-CODE naming',P+'''
01 WS-CUST-CODE PIC X(10). 01 WS-ORDR-CODE PIC X(10).
01 WS-EOF1 PIC X VALUE 'N'. 01 WS-EOF2 PIC X VALUE 'N'.
PROCEDURE DIVISION. OPEN INPUT F1 F2.
READ F1 AT END MOVE 'Y' TO WS-EOF1. READ F2 AT END MOVE 'Y' TO WS-EOF2.
PERFORM UNTIL WS-EOF1='Y' OR WS-EOF2='Y'
IF WS-CUST-CODE=WS-ORDR-CODE DISPLAY 'MATCH'
ELSE IF WS-CUST-CODE<WS-ORDR-CODE READ F1 AT END MOVE 'Y' TO WS-EOF1
ELSE READ F2 AT END MOVE 'Y' TO WS-EOF2 END-IF
END-PERFORM. CLOSE F1 F2. STOP RUN.''',check_matching=True)
test('M','eval','EVALUATE',P+'''
01 WS-K1 PIC X(10). 01 WS-K2 PIC X(10).
01 WS-E1 PIC X VALUE 'N'. 01 WS-E2 PIC X VALUE 'N'.
PROCEDURE DIVISION. OPEN INPUT F1 F2.
READ F1 AT END MOVE 'Y' TO WS-E1. READ F2 AT END MOVE 'Y' TO WS-E2.
PERFORM UNTIL WS-E1='Y' OR WS-E2='Y'
EVALUATE TRUE WHEN WS-K1=WS-K2 DISPLAY 'M' READ F1 AT END MOVE 'Y' TO WS-E1 READ F2 AT END MOVE 'Y' TO WS-E2
WHEN WS-K1<WS-K2 READ F1 AT END MOVE 'Y' TO WS-E1
WHEN OTHER READ F2 AT END MOVE 'Y' TO WS-E2
END-EVALUATE END-PERFORM. CLOSE F1 F2. STOP RUN.''',check_matching=True)
test('M','2stage','TWO-STAGE',P+'''
01 WS-KEY-A PIC X(10). 01 WS-KEY-B PIC X(10). 01 WS-KEY-C PIC X(10).
01 WS-E1 PIC X VALUE 'N'. 01 WS-E2 PIC X VALUE 'N'. 01 WS-E3 PIC X VALUE 'N'.
PROCEDURE DIVISION. OPEN INPUT F1 F2 F3 OUTPUT F4.
READ F1 AT END MOVE 'Y' TO WS-E1. READ F2 AT END MOVE 'Y' TO WS-E2.
PERFORM UNTIL WS-E1='Y' OR WS-E2='Y'
IF WS-KEY-A=WS-KEY-B WRITE R4 FROM R1 READ F1 AT END MOVE 'Y' TO WS-E1 READ F2 AT END MOVE 'Y' TO WS-E2
ELSE IF WS-KEY-A<WS-KEY-B READ F1 AT END MOVE 'Y' TO WS-E1
ELSE READ F2 AT END MOVE 'Y' TO WS-E2 END-IF
END-PERFORM. CLOSE F1 F2 F3 F4. STOP RUN.''',check_matching=True)
test('M','fp-add','FP:ADD non-match',P+'''
01 WS-KEY PIC 9(5). 01 WS-TOTAL PIC 9(5).
PROCEDURE DIVISION. MOVE 999 TO WS-KEY. ADD WS-KEY TO WS-TOTAL.
IF WS-TOTAL > 500 DISPLAY 'BIG' ELSE DISPLAY 'SMALL'. STOP RUN.''',check_matching=False)
test('M','fp-1file','FP:1 file only',P+'''
01 WS-KEY PIC X(10). 01 WS-EOF PIC X VALUE 'N'.
PROCEDURE DIVISION. OPEN INPUT F1.
READ F1 AT END MOVE 'Y' TO WS-EOF.
PERFORM UNTIL WS-EOF='Y' IF WS-KEY = SPACES DISPLAY 'EMPTY'
ELSE DISPLAY WS-KEY READ F1 AT END MOVE 'Y' TO WS-EOF
END-PERFORM. CLOSE F1. STOP RUN.''',check_matching=False)
test('M','fp-noopen','FP:no FILE at all',P+'''
01 WS-KEY PIC X(10).
PROCEDURE DIVISION. MOVE 'KEY' TO WS-KEY. DISPLAY WS-KEY. STOP RUN.''',check_matching=False)
test('M','fp-nokey','FP:no KEY var',P+'''
01 WS-EOF PIC X VALUE 'N'. 01 WS-TOTAL PIC 9(5).
PROCEDURE DIVISION. OPEN INPUT F1. READ F1 AT END MOVE 'Y' TO WS-EOF.
PERFORM UNTIL WS-EOF='Y' ADD 1 TO WS-TOTAL
READ F1 AT END MOVE 'Y' TO WS-EOF END-PERFORM. CLOSE F1. STOP RUN.''',check_matching=False)
test('M','fn-prevkey','WS-PREV-KEY valid',P+'''
01 WS-KEY PIC X(10). 01 WS-PREV-KEY PIC X(10) VALUE SPACES.
01 WS-EOF PIC X VALUE 'N'. 01 WS-DC PIC 9(4).
PROCEDURE DIVISION. OPEN INPUT F.
READ F AT END MOVE 'Y' TO WS-EOF.
PERFORM UNTIL WS-EOF='Y'
IF WS-KEY=WS-PREV-KEY ADD 1 TO WS-DC ELSE MOVE WS-KEY TO WS-PREV-KEY
READ F AT END MOVE 'Y' TO WS-EOF END-PERFORM. CLOSE F. STOP RUN.''',check_category='項目チェック(重複含む)')
# ════════════════════════════════════
# KEY BREAK series
# ════════════════════════════════════
print('\n--- KEY BREAK ---')
test('KB','ws-prev-key','WS-PREV-KEY+ACCUM',P+'''
01 WS-PREV-KEY PIC X(10). 01 WS-KEY PIC X(10).
01 WS-SUM PIC 9(7)V99. 01 WS-EOF PIC X VALUE 'N'.
PROCEDURE DIVISION. OPEN INPUT F.
READ F AT END MOVE 'Y' TO WS-EOF.
PERFORM UNTIL WS-EOF='Y'
IF WS-KEY NOT = WS-PREV-KEY
IF WS-PREV-KEY NOT = SPACES DISPLAY WS-PREV-KEY WS-SUM
MOVE WS-KEY TO WS-PREV-KEY MOVE 0 TO WS-SUM
ADD 1 TO WS-SUM READ F AT END MOVE 'Y' TO WS-EOF
END-PERFORM. CLOSE F. STOP RUN.''',check_category='項目チェック(重複含む)')
test('KB','fp-only-cnt','FP:CNT no match',P+'''
01 WS-ERR-PIC PIC X(10). 01 WS-CNT PIC 9(5).
PROCEDURE DIVISION. MOVE 'ABC' TO WS-ERR-PIC. DISPLAY WS-ERR-PIC. STOP RUN.''',check_matching=False)
# ════════════════════════════════════
# CONDITION BRANCH series
# ════════════════════════════════════
print('\n--- IF/EVALUATE ---')
test('IF','normal','IF-ELSE',P+'''
01 A PIC 9(5). 01 B PIC 9(5). 01 C PIC X(10).
PROCEDURE DIVISION. IF A > 100 AND B < 50 MOVE 'LARGE' TO C
ELSE IF A > 50 MOVE 'MEDIUM' TO C ELSE MOVE 'SMALL' TO C. DISPLAY C. STOP RUN.''',check_matching=False)
test('IF','not-eq','NOT =',P+'''
01 A PIC 9(5). 01 B PIC 9(5).
PROCEDURE DIVISION. IF A NOT = B DISPLAY 'DIFF' ELSE DISPLAY 'SAME'. STOP RUN.''',check_matching=False)
test('EV','normal','EVALUATE',P+'''
01 S PIC X(1). 01 R PIC X(10).
PROCEDURE DIVISION. EVALUATE S
WHEN 'A' MOVE 'ACTIVE' TO R WHEN 'I' MOVE 'INACTIVE' TO R
WHEN OTHER MOVE 'UNKNOWN' TO R END-EVALUATE. DISPLAY R. STOP RUN.''',check_matching=False)
test('EV','also','EVALUATE ALSO',P+'''
01 S PIC X(1). 01 T PIC X(1). 01 R PIC X(10).
PROCEDURE DIVISION. EVALUATE S ALSO T
WHEN 'A' ALSO 'X' MOVE 'A-X' TO R WHEN 'A' ALSO 'Y' MOVE 'A-Y' TO R
WHEN OTHER MOVE 'OTHER' TO R END-EVALUATE. DISPLAY R. STOP RUN.''',check_matching=False)
# ════════════════════════════════════
# DIVIDE series
# ════════════════════════════════════
print('\n--- DIVIDE ---')
test('DV','50','DIVIDE 50',P+'''
01 V PIC 9(5) VALUE 100. 01 R PIC 9(5). 01 RM PIC 9(5).
PROCEDURE DIVISION. DIVIDE 50 INTO V GIVING R REMAINDER RM.
IF R = 2 DISPLAY 'OK'. STOP RUN.''',check_category='DIVIDE_50.0')
test('DV','25','DIVIDE 25',P+'''
01 V PIC 9(5) VALUE 100. 01 R PIC 9(5). 01 RM PIC 9(5).
PROCEDURE DIVISION. DIVIDE 25 INTO V GIVING R REMAINDER RM.
IF R = 4 DISPLAY 'OK'. STOP RUN.''',check_category='DIVIDE_25.0')
test('DV','100','DIVIDE 100',P+'''
01 V PIC 9(5) VALUE 10000. 01 R PIC 9(5). 01 RM PIC 9(5).
PROCEDURE DIVISION. DIVIDE 100 INTO V GIVING R REMAINDER RM.
IF R = 100 DISPLAY 'OK'. STOP RUN.''',check_category='DIVIDE_100.0')
test('DV','fp-var','FP:var name 50',P+'''
01 WS-50 PIC 9(5). 01 V PIC 9(5) VALUE 100.
PROCEDURE DIVISION. MOVE 30 TO WS-50. DIVIDE WS-50 INTO V. STOP RUN.''',check_matching=False)
test('DV','fp-mul','FP:MULTIPLY',P+'''
01 A PIC 9(5) VALUE 50. PROCEDURE DIVISION.
MULTIPLY 3 BY A. IF A=150 DISPLAY 'OK'. STOP RUN.''',check_matching=False)
# ════════════════════════════════════
# CICS online
# ════════════════════════════════════
print('\n--- CICS ---')
test('CICS','map','MAP var',P+'''
01 WS-MAP PIC X(10). 01 WS-CA PIC X(100).
PROCEDURE DIVISION. IF WS-MAP = 'MAP01' DISPLAY 'OK'. STOP RUN.''',check_category='online')
test('CICS','dfh','DFHCOMMAREA',P+'''
01 WS-CA PIC X(100). 01 WS-RESP PIC S9(8) COMP.
PROCEDURE DIVISION.
*> EXEC CICS LINK PROGRAM('PGM1') COMMAREA(WS-CA) RESP(WS-RESP) END-EXEC.
IF WS-RESP = 0 DISPLAY 'OK'. STOP RUN.''',check_matching=False) # comment stripped
test('CICS','fp-no','FP:no keyword',P+'''
01 WS-DATA PIC X(100).
PROCEDURE DIVISION. MOVE 'CICS' TO WS-DATA. DISPLAY WS-DATA. STOP RUN.''',check_matching=False)
# ════════════════════════════════════
# SEARCH ALL
# ════════════════════════════════════
print('\n--- SEARCH ---')
test('SR','all','SEARCH ALL',P+'''
01 TBL. 05 E OCCURS 10 TIMES ASCENDING KEY IS EID INDEXED BY IX.
10 EID PIC 9(03). 10 ENM PIC X(10).
01 S PIC 9(03). 01 F PIC X VALUE 'N'.
PROCEDURE DIVISION. MOVE 5 TO S. SEARCH ALL E
AT END DISPLAY 'NOT FOUND' WHEN EID(IX)=S MOVE 'Y' TO F. STOP RUN.''',check_matching=False)
# ════════════════════════════════════
# SORT/MERGE
# ════════════════════════════════════
print('\n--- SORT/MERGE ---')
test('SRT','asc','SORT ASC',P+'''
01 WS-DATA PIC X(80).
PROCEDURE DIVISION. SORT SF ON ASCENDING KEY SK USING F1 GIVING FO. STOP RUN.''',check_category='SORT')
test('SRT','desc','SORT DESC',P+'''
01 WS-DATA PIC X(80).
PROCEDURE DIVISION. SORT SF ON DESCENDING KEY SK USING F1 GIVING FO. STOP RUN.''',check_category='SORT')
test('SRT','multi','SORT multi-key',P+'''
01 WS-DATA PIC X(80).
PROCEDURE DIVISION. SORT SF ON ASCENDING KEY K1 K2 USING F1 GIVING FO. STOP RUN.''',check_category='SORT')
test('MRG','normal','MERGE',P+'''
01 WS-DATA PIC X(80).
PROCEDURE DIVISION. MERGE MF ON ASCENDING KEY MK USING F1 F2 GIVING FO. STOP RUN.''',check_category='MERGE')
test('SRT','fp','FP:no SORT',P+'''
01 WS-DATA PIC X(80).
PROCEDURE DIVISION. MOVE 'SORT KEY' TO WS-DATA. DISPLAY WS-DATA. STOP RUN.''',check_matching=False)
# ════════════════════════════════════
# L1 DIRECT TYPES
# ════════════════════════════════════
print('\n--- L1 DIRECT ---')
test('L1','sql','EXEC SQL',P+'''
01 WS-ID PIC X(10). PROCEDURE DIVISION.
EXEC SQL SELECT * FROM TBL WHERE ID=:WS-ID END-EXEC. STOP RUN.''',check_category='DB操作')
test('L1','sql-cmt','*>EXEC SQL comment',P+'''
01 WS-DATA PIC X(10). PROCEDURE DIVISION.
*> EXEC SQL SELECT * FROM TBL END-EXEC. MOVE 'X' TO WS-DATA. STOP RUN.''',check_matching=False)
test('L1','sql-literal','FP:SQL in literal',P+'''
01 WS-MSG PIC X(50). PROCEDURE DIVISION.
MOVE 'EXEC SQL SELECT * FROM TBL' TO WS-MSG. STOP RUN.''',check_matching=False)
test('L1','call','CALL+LINKAGE',P+'''
01 WS-P PIC X(10). LINKAGE SECTION. 01 LS-P PIC X(10).
PROCEDURE DIVISION USING LS-P. CALL 'SUB' USING WS-P. STOP RUN.''',check_category='子程序调用')
test('L1','call-only','FP:CALL no LINKAGE',P+'''
01 WS-P PIC 9(5). PROCEDURE DIVISION. CALL 'SUB' USING WS-P. STOP RUN.''',check_matching=False)
test('L1','link-only','FP:LINKAGE no CALL',P+'''
01 WS-X PIC 9(5). LINKAGE SECTION. 01 LS-P PIC X(10).
PROCEDURE DIVISION USING LS-P. MOVE 'X' TO LS-P. GOBACK.''',check_matching=False)
test('L1','init','IS INITIAL',P+'''01 C PIC 9(5) VALUE 0.
PROCEDURE DIVISION. ADD 1 TO C. DISPLAY C. STOP RUN.
IDENTIFICATION DIVISION. PROGRAM-ID. PGM IS INITIAL.''',check_category='IS INITIAL')
test('L1','sys','SYSIN',P+'''01 D PIC X(80). PROCEDURE DIVISION.
ACCEPT D FROM SYSIN. DISPLAY D. STOP RUN.''',check_category='SYSIN')
test('L1','sys-var','FP:SYSIN variable',P+'''01 SYSIN PIC X(80).
PROCEDURE DIVISION. MOVE 'DATA' TO SYSIN. DISPLAY SYSIN. STOP RUN.''',check_matching=False)
test('L1','enc','encoding',P+'''01 A PIC X(10) VALUE 'ABCDEF'. 01 E PIC X(10).
PROCEDURE DIVISION. MOVE 'ABC' TO A. DISPLAY A. STOP RUN.''',check_matching=False)
test('L1','wrt-after','WRITE AFTER',P+'''01 R PIC X(50).
PROCEDURE DIVISION. OPEN OUTPUT F. WRITE R AFTER ADVANCING 1 LINE. CLOSE F. STOP RUN.''',check_category='编辑输出')
test('L1','wrt-before','WRITE BEFORE',P+'''01 R PIC X(50).
PROCEDURE DIVISION. OPEN OUTPUT F. WRITE R BEFORE ADVANCING 2 LINES. CLOSE F. STOP RUN.''',check_category='编辑输出')
test('L1','org','ORGANIZATION IS',P+'''PROCEDURE DIVISION. STOP RUN.
ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL.
SELECT F ASSIGN TO 'F.DAT' ORGANIZATION IS INDEXED.''',check_category='文件编成')
test('L1','alt','ALTERNATE KEY',P+'''PROCEDURE DIVISION. STOP RUN.
ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL.
SELECT F ASSIGN TO 'F.DAT' ALTERNATE RECORD KEY IS AK.''',check_category='替代索引')
test('L1','alt-org','ALT+ORG conflict',P+'''PROCEDURE DIVISION. STOP RUN.
ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL.
SELECT F ASSIGN TO 'F.DAT' ORGANIZATION IS INDEXED
RECORD KEY IS RK ALTERNATE RECORD KEY IS AK.''',check_category='替代索引')
# ════════════════════════════════════
# CSV series
# ════════════════════════════════════
print('\n--- CSV ---')
test('CSV','merge','CSV merge',P+'''
01 F1 PIC X(10) VALUE 'A'. 01 F2 PIC X(10) VALUE 'B'.
01 C PIC X(50). 01 P PIC 9(3) VALUE 1.
PROCEDURE DIVISION. STRING F1 DELIMITED BY SPACES ',' DELIMITED BY SIZE
F2 DELIMITED BY SPACES INTO C WITH POINTER P. DISPLAY C. STOP RUN.''',check_category='CSV合并')
test('CSV','split','CSV split',P+'''
01 L PIC X(50) VALUE 'A,B,C'. 01 C PIC 9(3).
PROCEDURE DIVISION. INSPECT L TALLYING C FOR ALL ','.
INSPECT L REPLACING ALL ',' BY '|'. DISPLAY L. STOP RUN.''',check_category='CSV拆分')
test('CSV','fp-str','FP:STRING no CSV',P+'''
01 A PIC X(5) VALUE 'HELLO'. 01 B PIC X(5) VALUE 'WORLD'.
01 R PIC X(50). 01 P PIC 9(3) VALUE 1.
PROCEDURE DIVISION. STRING A DELIMITED BY SPACES ' ' DELIMITED BY SIZE
B DELIMITED BY SPACES INTO R WITH POINTER P. STOP RUN.''',check_matching=False)
test('CSV','fp-insp','FP:INSPECT no CSV',P+'''
01 T PIC X(30) VALUE 'AAABBB'. 01 C PIC 9(3).
PROCEDURE DIVISION. INSPECT T TALLYING C FOR ALL 'A'. DISPLAY C. STOP RUN.''',check_matching=False)
# ════════════════════════════════════
# EDIT PROCESSING
# ════════════════════════════════════
print('\n--- EDIT ---')
test('EDIT','ws-err','WS-ERR field',P+'''
01 WS-ERR-CODE PIC 9(4). 01 WS-V PIC 9(5).
PROCEDURE DIVISION. IF WS-V = 0 MOVE 9999 TO WS-ERR-CODE ELSE DISPLAY 'OK'. STOP RUN.''',check_category='編集処理(校验)')
test('EDIT','fp','FP:no ERR',P+'''
01 WS-V PIC 9(5). PROCEDURE DIVISION. MOVE 1 TO WS-V. DISPLAY WS-V. STOP RUN.''',check_matching=False)
print('\n'+'='*95)
print(f'RESULT: {STATS["pass"]} PASS / {STATS["fail"]} FAIL / {STATS["total"]} TOTAL')
print('='*95)
if STATS["fail"] > 0:
for tid, s in sorted(STATS["by_type"].items()):
print(f' {tid}: {s["pass"]}/{s["total"]} ({s["pass"]/max(s["total"],1)*100:.0f}%)')
sys.exit(1)