feat: DB管线补全 + 新增orchestrator_db/program_schema/to_sql + 清理临时脚本
This commit is contained in:
+265
-12
@@ -101,10 +101,51 @@ def _cap_paths_fair(new_active, child_paths):
|
||||
|
||||
# ── 路径枚举 ──
|
||||
|
||||
|
||||
def eval_true_branch_constraints(when_value: str, fields: list) -> tuple:
|
||||
"""解析 EVALUATE TRUE 的 WHEN 条件,返回 (true_set, false_sets)。
|
||||
|
||||
true_set: list[Constraint] — 使此 WHEN 为 True 的一组约束
|
||||
false_sets: list[list[Constraint]] — 使此 WHEN 为 False 的 MC/DC 倒集
|
||||
|
||||
适用于 EVALUATE TRUE 的所有 WHEN 类型:
|
||||
- 简单条件: WS-STATUS = '9' → 直接产生 (T, [F])
|
||||
- CondNot: NOT WS-STATUS = '1' → (翻转, [翻转倒])
|
||||
- 复合条件: WS-STATUS = '1' AND WS-APPL-ID = 0 → MC/DC 约束集
|
||||
"""
|
||||
cond = parse_compound_condition(when_value, fields)
|
||||
|
||||
if cond and isinstance(cond, CondLeaf) and is_field(cond.field, fields):
|
||||
t = [(cond.field, cond.op, cond.value, True)]
|
||||
f = [[(cond.field, cond.op, cond.value, False)]]
|
||||
return t, f
|
||||
|
||||
if cond and isinstance(cond, CondNot) and isinstance(cond.child, CondLeaf) and is_field(cond.child.field, fields):
|
||||
leaf = cond.child
|
||||
t = [(leaf.field, leaf.op, leaf.value, False)]
|
||||
f = [[(leaf.field, leaf.op, leaf.value, True)]]
|
||||
return t, f
|
||||
|
||||
leaves = collect_leaves(cond) if cond else []
|
||||
if leaves and all(is_field(l.field, fields) for l in leaves):
|
||||
sets = mcdc_sets(cond, fields)
|
||||
if sets:
|
||||
true_sets = [list(cs) for cs, decision in sets if decision]
|
||||
false_sets = [list(cs) for cs, decision in sets if not decision]
|
||||
if true_sets:
|
||||
return true_sets[0], false_sets
|
||||
|
||||
return [], []
|
||||
|
||||
|
||||
_enum_counter = 0
|
||||
def enum_paths(node, fields):
|
||||
global _enum_counter
|
||||
_enum_counter += 1
|
||||
"""枚举路径,每条路径返回 (constraints, assignments).
|
||||
返回 list[tuple[list[tuple], dict]].
|
||||
"""
|
||||
pass
|
||||
if isinstance(node, Assign):
|
||||
return [([], {node.target: [node.source_info]})]
|
||||
|
||||
@@ -199,6 +240,16 @@ def enum_paths(node, fields):
|
||||
for fp_cons, fp_assign in (false_sub or [([], {})]):
|
||||
paths.append(([(field, op, val, False)] + fp_cons, fp_assign))
|
||||
return paths
|
||||
# Fallback: unparseable condition (e.g. FUNCTION MOD) — still traverse both branches
|
||||
if node.true_seq or node.false_seq:
|
||||
paths = []
|
||||
ts = enum_paths(node.true_seq, fields)
|
||||
for sp_cons, sp_assign in (ts or [([], {})]):
|
||||
paths.append((sp_cons, sp_assign))
|
||||
fs = enum_paths(node.false_seq, fields)
|
||||
for fp_cons, fp_assign in (fs or [([], {})]):
|
||||
paths.append((fp_cons, fp_assign))
|
||||
return paths if paths else [([], {})]
|
||||
return [([], {})]
|
||||
|
||||
elif isinstance(node, BrEval):
|
||||
@@ -260,11 +311,14 @@ def enum_paths(node, fields):
|
||||
if not new_false_sets:
|
||||
prior_false_sets = []
|
||||
break
|
||||
combined = []
|
||||
for pf_set in prior_false_sets:
|
||||
for nf_set in new_false_sets:
|
||||
combined.append(list(pf_set) + list(nf_set))
|
||||
prior_false_sets = combined
|
||||
if not prior_false_sets:
|
||||
prior_false_sets = list(new_false_sets)
|
||||
else:
|
||||
combined = []
|
||||
for pf_set in prior_false_sets:
|
||||
for nf_set in new_false_sets:
|
||||
combined.append(list(pf_set) + list(nf_set))
|
||||
prior_false_sets = combined
|
||||
else:
|
||||
prior_false_sets = []
|
||||
break
|
||||
@@ -334,6 +388,8 @@ def enum_paths(node, fields):
|
||||
if parsed and is_field(parsed[0], fields):
|
||||
field, op, val = parsed
|
||||
paths = []
|
||||
# Skip 路径放在首位,确保不被 _cap_paths 截断丢失
|
||||
paths.append(([(field, op, val, True)], {}))
|
||||
false_sub = _cap_paths(enum_paths(node.body_seq, fields))
|
||||
false_sub = [([c for c in cons if c is not _STOP_EXIT_PERFORM], a) for cons, a in false_sub]
|
||||
for sp_cons, sp_assign in (false_sub or [([], {})]):
|
||||
@@ -383,7 +439,6 @@ def enum_paths(node, fields):
|
||||
paths.append((the_cons + sp_cons, merged_max))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
paths.append(([(field, op, val, True)], {}))
|
||||
return paths
|
||||
# 尝试复合条件(AND/OR)
|
||||
cond_tree = parse_compound_condition(node.condition, fields)
|
||||
@@ -393,6 +448,10 @@ def enum_paths(node, fields):
|
||||
sets = mcdc_sets(cond_tree, fields)
|
||||
if sets:
|
||||
paths = []
|
||||
# Skip (True) 路径放在首位,确保不被 _cap_paths 截断丢失
|
||||
for constraints, decision in sets:
|
||||
if decision:
|
||||
paths.append((list(constraints), {}))
|
||||
false_sub = _cap_paths(enum_paths(node.body_seq, fields))
|
||||
false_sub = [([c for c in cons if c is not _STOP_EXIT_PERFORM], a) for cons, a in false_sub]
|
||||
for sp_cons, sp_assign in (false_sub or [([], {})]):
|
||||
@@ -409,11 +468,27 @@ def enum_paths(node, fields):
|
||||
for constraints, decision in sets:
|
||||
if not decision:
|
||||
paths.append((list(constraints) + sp_cons, sp_assign))
|
||||
for constraints, decision in sets:
|
||||
if decision:
|
||||
paths.append((list(constraints), {}))
|
||||
if paths:
|
||||
return paths
|
||||
# 单叶子 fallback(不可识别/未知字段)
|
||||
if len(leaves) == 1:
|
||||
leaf = leaves[0]
|
||||
paths = []
|
||||
# Skip 路径放在首位,确保不被 _cap_paths 截断丢失
|
||||
paths.append(([(leaf.field, leaf.op, leaf.value, True)], {}))
|
||||
body_paths = _cap_paths(enum_paths(node.body_seq, fields))
|
||||
body_paths = [([c for c in cons if c is not _STOP_EXIT_PERFORM], a) for cons, a in body_paths]
|
||||
for sp_cons, sp_assign in (body_paths or [([], {})]):
|
||||
if node.varying_from and node.varying_var:
|
||||
from_asgn = {'type': 'move_literal', 'literal': node.varying_from}
|
||||
from_assign = {node.varying_var: [from_asgn]}
|
||||
merged = {}
|
||||
for d in (from_assign, sp_assign):
|
||||
for k, v in d.items():
|
||||
merged.setdefault(k, []).extend(v if isinstance(v, list) else [v])
|
||||
sp_assign = merged
|
||||
paths.append(([(leaf.field, leaf.op, leaf.value, False)] + sp_cons, sp_assign))
|
||||
return paths
|
||||
return [([], {})]
|
||||
|
||||
elif isinstance(node, CallNode):
|
||||
@@ -649,6 +724,116 @@ def make_base_record(seq_num: int, fields: list) -> dict:
|
||||
return rec
|
||||
|
||||
|
||||
def _resolve_field_value(field_name, rec, fields):
|
||||
"""将字段名解析为当前记录值。
|
||||
对组项目(无 PIC)拼接其基本子字段的值。
|
||||
返回字符串值,或在无法解析时返回 None。
|
||||
"""
|
||||
for f in fields:
|
||||
if f['name'] == field_name:
|
||||
if f.get('pic'):
|
||||
return str(rec.get(field_name, ''))
|
||||
else:
|
||||
children = _children_of(field_name, fields)
|
||||
parts = []
|
||||
for c in children:
|
||||
if c.get('pic'):
|
||||
parts.append(str(rec.get(c['name'], '')))
|
||||
return ''.join(parts) if parts else None
|
||||
return None
|
||||
|
||||
|
||||
def _expand_group_constraint(rec, field_name, operator, value, want_true, fields, assignments=None, path_assign=None):
|
||||
"""将组项目间的比较约束展开为子字段约束。
|
||||
|
||||
COBOL 组项目比较 = 逐子字段字典序比较(先比较第一个子字段,
|
||||
若相等则继续比较下一个)。
|
||||
|
||||
策略:
|
||||
- >= True: 让第一个子字段 > 对应右侧子字段
|
||||
- >= False (<): 让第一个子字段 < 对应右侧子字段
|
||||
- = True: 让所有子字段逐个相等
|
||||
- = False (<>): 让第一个子字段 != 对应右侧子字段
|
||||
"""
|
||||
field_children = _children_of(field_name, fields)
|
||||
elementary = [c for c in field_children if c.get('pic')]
|
||||
if not elementary:
|
||||
return False
|
||||
|
||||
# 解析右侧值:如果是字段名,找到其子字段或值
|
||||
right_children = []
|
||||
if any(f['name'] == value for f in fields):
|
||||
for f in fields:
|
||||
if f['name'] == value:
|
||||
if f.get('pic'):
|
||||
# 基本字段:直接用其值
|
||||
right_val = _resolve_field_value(value, rec, fields)
|
||||
if right_val is not None:
|
||||
if operator in ('>=', '>') and want_true:
|
||||
apply_constraint(rec, elementary[0]['name'], '>', right_val, True, fields, assignments, path_assign)
|
||||
return True
|
||||
elif operator in ('>=', '>') and not want_true:
|
||||
apply_constraint(rec, elementary[0]['name'], '<', right_val, True, fields, assignments, path_assign)
|
||||
return True
|
||||
elif operator == '=' and want_true:
|
||||
apply_constraint(rec, elementary[0]['name'], '=', right_val, True, fields, assignments, path_assign)
|
||||
return True
|
||||
elif operator == '=' and not want_true:
|
||||
apply_constraint(rec, elementary[0]['name'], '<>', right_val, True, fields, assignments, path_assign)
|
||||
return True
|
||||
else:
|
||||
# 组项目:找对应子字段
|
||||
right_children = [c for c in _children_of(value, fields) if c.get('pic')]
|
||||
break
|
||||
|
||||
if not right_children:
|
||||
# value 不是字段名(字面量)或无法解析,直接用值
|
||||
right_children = elementary # 使用同样的子字段结构,各自对比值
|
||||
|
||||
min_len = min(len(elementary), len(right_children))
|
||||
if min_len == 0:
|
||||
return False
|
||||
|
||||
if operator in ('>=', '>') and want_true:
|
||||
first = elementary[0]
|
||||
# 取第一个右侧子字段的值
|
||||
right_val = _resolve_field_value(right_children[0]['name'], rec, fields)
|
||||
if right_val:
|
||||
apply_constraint(rec, first['name'], '>', right_val, True, fields, assignments, path_assign)
|
||||
else:
|
||||
apply_constraint(rec, first['name'], '>=', str(right_children[0]['name']), True, fields, assignments, path_assign)
|
||||
return True
|
||||
|
||||
elif operator in ('>=', '>') and not want_true:
|
||||
first = elementary[0]
|
||||
right_val = _resolve_field_value(right_children[0]['name'], rec, fields)
|
||||
if right_val:
|
||||
apply_constraint(rec, first['name'], '<', right_val, True, fields, assignments, path_assign)
|
||||
else:
|
||||
apply_constraint(rec, first['name'], '<', str(right_children[0]['name']), True, fields, assignments, path_assign)
|
||||
return True
|
||||
|
||||
elif operator == '=' and want_true:
|
||||
for i in range(min_len):
|
||||
right_val = _resolve_field_value(right_children[i]['name'], rec, fields)
|
||||
if right_val:
|
||||
apply_constraint(rec, elementary[i]['name'], '=', right_val, True, fields, assignments, path_assign)
|
||||
else:
|
||||
apply_constraint(rec, elementary[i]['name'], '=', str(right_children[i]['name']), True, fields, assignments, path_assign)
|
||||
return True
|
||||
|
||||
elif operator == '=' and not want_true:
|
||||
first = elementary[0]
|
||||
right_val = _resolve_field_value(right_children[0]['name'], rec, fields)
|
||||
if right_val:
|
||||
apply_constraint(rec, first['name'], '<>', right_val, True, fields, assignments, path_assign)
|
||||
else:
|
||||
apply_constraint(rec, first['name'], '<>', str(right_children[0]['name']), True, fields, assignments, path_assign)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# ── 约束应用 ──
|
||||
|
||||
def _check_constraint_satisfied(rec, field_name, operator, value, want_true, fields):
|
||||
@@ -862,6 +1047,46 @@ def _reconcile_unstring_fields(rec, left_field, operator, right_field, want_true
|
||||
logger.debug(f"字段间比较协调:{left_field}={left_val} {operator} {right_field} -> {right_root}={rec[right_root]} (want={want_true})")
|
||||
|
||||
|
||||
def _apply_redefines_child_constraint(rec, field_name, operator, value, want_true, fields, parent_name):
|
||||
"""Apply constraint on a group REDEFINES child by computing parent value."""
|
||||
# Find the child's pic_info and compute satisfying value
|
||||
child_pi = None
|
||||
offset = 0
|
||||
total_len = 0
|
||||
child_len = 0
|
||||
for f in fields:
|
||||
if f.get('redefines') and not f.get('pic') and f['redefines'] == parent_name:
|
||||
redef_children = _children_of(f['name'], fields)
|
||||
for c in redef_children:
|
||||
c_len = (c.get('pic_info', {}).get('digits', 0) + c.get('pic_info', {}).get('decimal', 0)
|
||||
or c.get('pic_info', {}).get('length', 0))
|
||||
if c['name'] == field_name:
|
||||
child_pi = c.get('pic_info', {})
|
||||
child_len = c_len
|
||||
break
|
||||
offset += c_len
|
||||
total_len = offset + sum(
|
||||
(cc.get('pic_info', {}).get('digits', 0) + cc.get('pic_info', {}).get('decimal', 0)
|
||||
or cc.get('pic_info', {}).get('length', 0))
|
||||
for cc in redef_children[redef_children.index(c):]
|
||||
) if child_pi else 0
|
||||
break
|
||||
|
||||
if not child_pi:
|
||||
return
|
||||
|
||||
val = satisfying_value(child_pi, operator, value, want_true)
|
||||
val = val.zfill(child_len)[:child_len]
|
||||
|
||||
# Merge into parent's current value
|
||||
parent_val = str(rec.get(parent_name, ''))
|
||||
if len(parent_val) < offset + child_len:
|
||||
parent_val = parent_val.ljust(offset + child_len, '0')
|
||||
parent_val = parent_val[:offset] + val + parent_val[offset + child_len:]
|
||||
|
||||
# Apply the combined constraint to the parent
|
||||
apply_constraint(rec, parent_name, '=', f'"{parent_val}"', True, fields)
|
||||
|
||||
def apply_constraint(rec, field_name, operator, value, want_true, fields, assignments=None, path_assign=None):
|
||||
# 标准化字段名:去除括号内空格(WS-CELL ( 1, 1 ) → WS-CELL(1,1))
|
||||
field_name = re.sub(r'\s*([(),])\s*', r'\1', field_name)
|
||||
@@ -896,6 +1121,17 @@ def apply_constraint(rec, field_name, operator, value, want_true, fields, assign
|
||||
apply_constraint(rec, parent_name, operator, value, want_true, fields, assignments, path_assign)
|
||||
return
|
||||
break
|
||||
|
||||
# 组 REDEFINES 子字段:通过父字段传播约束
|
||||
for f in fields:
|
||||
if f.get('redefines') and not f.get('pic'):
|
||||
redef_children = _children_of(f['name'], fields)
|
||||
if any(c['name'] == field_name for c in redef_children):
|
||||
parent_name = f['redefines']
|
||||
logger.debug(f"组 REDEFINES 子字段约束: {field_name} → {parent_name}")
|
||||
_apply_redefines_child_constraint(rec, field_name, operator, value, want_true, fields, parent_name)
|
||||
return
|
||||
|
||||
chain = None
|
||||
if assignments:
|
||||
root_var, chain = trace_to_root(field_name, assignments, fields, path_assign)
|
||||
@@ -904,6 +1140,14 @@ def apply_constraint(rec, field_name, operator, value, want_true, fields, assign
|
||||
if any(f['name'] == new_field_name for f in fields):
|
||||
field_name, operator, value = new_field_name, new_op, new_val
|
||||
|
||||
# 组项目展开:当 field_name 是组项目(无 PIC)时,展开为子字段约束
|
||||
field_def = next((f for f in fields if f['name'] == field_name), None)
|
||||
if field_def and not field_def.get('pic'):
|
||||
expanded = _expand_group_constraint(rec, field_name, operator, value, want_true,
|
||||
fields, assignments, path_assign)
|
||||
if expanded:
|
||||
return
|
||||
|
||||
# 字段间比较:在 satisfied check 前解析/处理
|
||||
if any(f['name'] == value for f in fields):
|
||||
resolved_literal = None
|
||||
@@ -921,8 +1165,13 @@ def apply_constraint(rec, field_name, operator, value, want_true, fields, assign
|
||||
_apply_arith_constraint(rec, field_name, operator, value, want_true, fields)
|
||||
return
|
||||
else:
|
||||
logger.debug(f"字段间比较约束跳过:{field_name} {operator} {value}")
|
||||
return
|
||||
# 尝试将字段名值解析为记录值
|
||||
resolved_val = _resolve_field_value(value, rec, fields)
|
||||
if resolved_val is not None:
|
||||
value = resolved_val
|
||||
else:
|
||||
logger.debug(f"字段间比较约束跳过:{field_name} {operator} {value}")
|
||||
return
|
||||
|
||||
# 如果当前值已满足该约束,跳过覆盖(保持先前约束的一致性)
|
||||
# 但零值时强制使用边界值(非 0/非 min)
|
||||
@@ -1114,7 +1363,11 @@ def _enum_search_paths(node, fields):
|
||||
for k, v in sp_assign.items():
|
||||
merged_assign.setdefault(k, []).extend(v if isinstance(v, list) else [v])
|
||||
if cond_tree and isinstance(cond_tree, CondLeaf):
|
||||
paths.append(([(elem_key, cond_tree.op, matching_val, True)] + sp_cons, merged_assign))
|
||||
# Also set the subject field (right side of comparison) to match
|
||||
subj = cond_tree.value
|
||||
if any(f['name'] == subj for f in fields):
|
||||
merged_assign[subj] = [{'type': 'move_literal', 'literal': matching_val}]
|
||||
paths.append(([(elem_key, cond_tree.op, matching_val.rstrip(), True)] + sp_cons, merged_assign))
|
||||
else:
|
||||
paths.append((sp_cons, merged_assign))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user