merge local cobol_testgen improvements into v3 shared modules

- cond.py: SQLCODE/SQLSTATE handling, alphanumeric >/< boundary fix
- output.py: termination tracking, db_input support, _is_field_assigned filter
- coverage.py: mark_from_gcov, THRU support, KeyError protection
- gcov.py: new file (dependency for coverage.py)
- grammar.lark: multi-segment PIC support
- read.py: SQL INCLUDE resolution, DECLARE TABLE parsing, * comment fix
- core.py: SQL parsing, blocked_names, keyword list
- design.py: multi-sentinel, THRU ranges, PERFORM VARYING last iteration
- __init__.py: local main() + v3 API functions, guarded imports

All 6 ZAN programs verified passing through v3 pipeline
This commit is contained in:
hangshuo652
2026-06-23 22:38:17 +08:00
parent e5ab3baa46
commit 7fb9304212
9 changed files with 1595 additions and 326 deletions
+68 -25
View File
@@ -23,27 +23,68 @@ def _scenario_text(path_cons):
return ', '.join(parts)
def _write_json(entries, outpath):
if not entries:
return
outpath.parent.mkdir(parents=True, exist_ok=True)
with open(outpath, 'w', encoding='utf-8') as f:
json.dump(entries, f, ensure_ascii=False, indent=2)
def _is_field_assigned(fname, assigned_set, fields, fd_fields_lookup):
if not assigned_set:
return False
if fname in assigned_set:
return True
level_map = {}
name_order = []
for f in fields:
fn = f['name']
lv = f.get('level', 77)
level_map[fn] = lv
name_order.append((lv, fn))
flv = level_map.get(fname, 77)
ancestor = None
for lv, fn in name_order:
if fn == fname:
break
if lv < flv:
ancestor = fn
if ancestor and ancestor in assigned_set:
return True
return False
def output_json(records, outpath, roles=None, fd_fields=None, field_to_fd=None,
open_dir=None, path_cons_list=None):
open_dir=None, term_types=None, db_input=None, data_fields=None):
outpath.parent.mkdir(parents=True, exist_ok=True)
if not roles:
out = []
for i, rec in enumerate(records):
entry = dict(rec)
entry['termination'] = (term_types or ['normal'] * len(records))[i]
out.append(entry)
obj = {'program': outpath.stem, 'records': out}
if db_input:
obj['db_input'] = db_input
with open(outpath, 'w', encoding='utf-8') as f:
json.dump(records, f, ensure_ascii=False, indent=2)
json.dump(obj, f, ensure_ascii=False, indent=2)
return
# FD direction lookup
term_types = term_types or ['normal'] * len(records)
out = []
for i, rec in enumerate(records):
inp = {}
out_exp = {}
ws = {}
# Group by FD
if fd_fields and field_to_fd:
for fd_name, fds_set in fd_fields.items():
direction = (open_dir or {}).get(fd_name, '')
inp_block = {}
out_block = {}
assigned_set = rec.get('_assigned_fields', set())
for fname in fds_set:
if fname not in rec:
continue
@@ -52,13 +93,13 @@ def output_json(records, outpath, roles=None, fd_fields=None, field_to_fd=None,
if direction in ('INPUT', 'I-O') and r in ('input', 'inout'):
inp_block[fname] = val
if direction in ('OUTPUT', 'I-O') and r in ('output', 'inout'):
out_block[fname] = val
if _is_field_assigned(fname, assigned_set, data_fields or [], fd_fields):
out_block[fname] = val
if inp_block:
inp[fd_name] = inp_block
if out_block:
out_exp[fd_name] = out_block
# Working-storage: not belonging to any FD
for name, val in rec.items():
if not field_to_fd or name not in field_to_fd:
ws[name] = val
@@ -66,25 +107,21 @@ def output_json(records, outpath, roles=None, fd_fields=None, field_to_fd=None,
entry = {
'input': inp,
'expected_output': out_exp,
'working_storage': ws,
'working_storage': {k: v for k, v in ws.items() if k != '_assigned_fields'},
'termination': term_types[i] if i < len(term_types) else 'normal',
}
if path_cons_list and i < len(path_cons_list):
text = _scenario_text(path_cons_list[i])
if text:
entry['scenario'] = text
out.append(entry)
with open(outpath, 'w', encoding='utf-8') as f:
json.dump(out, f, ensure_ascii=False, indent=2)
obj = {'program': outpath.stem, 'records': out}
if db_input:
obj['db_input'] = db_input
_write_json(obj, outpath)
def output_input_files(records, outdir, stem, roles, fd_fields, field_to_fd, open_dir):
"""按 FD 名拆分出力入力 JSON 文件。
每个 INPUT / I-O 方向 FD 生成一个文件:{stem}_{fd_name}.json
内容为路径数 × 记录,每条只含该 FD 的入力字段值。
"""
def output_input_files(records, outdir, stem, roles, fd_fields, field_to_fd, open_dir,
term_types=None):
term_types = term_types or ['normal'] * len(records)
input_fds = {}
for fd_name, fds_set in fd_fields.items():
direction = (open_dir or {}).get(fd_name, '')
@@ -101,9 +138,11 @@ def output_input_files(records, outdir, stem, roles, fd_fields, field_to_fd, ope
outdir.mkdir(parents=True, exist_ok=True)
for fd_name, fds_set in input_fds.items():
fd_records = []
normals = []
abends = []
direction = (open_dir or {}).get(fd_name, '')
for rec in records:
for i, rec in enumerate(records):
term = term_types[i] if i < len(term_types) else 'normal'
fd_rec = {}
for fname in fds_set:
r = roles.get(fname, 'unused')
@@ -111,8 +150,12 @@ def output_input_files(records, outdir, stem, roles, fd_fields, field_to_fd, ope
if fname in rec:
fd_rec[fname] = rec[fname]
if fd_rec:
fd_records.append(fd_rec)
if term == 'abend':
abends.append(fd_rec)
else:
normals.append(fd_rec)
outpath = outdir / f'{stem}_{fd_name}.json'
with open(outpath, 'w', encoding='utf-8') as f:
json.dump(fd_records, f, ensure_ascii=False, indent=2)
if normals:
_write_json(normals, outdir / f'{stem}_{fd_name}.json')
if abends:
_write_json(abends, outdir / f'{stem}_abend_{fd_name}.json')