"""Bridge: procedure_parser -> BrSeq/BrIf/BrEval tree pipeline integration. Primary: new procedure_parser (fast, deterministic, no path explosion). Fallback: old BrParser (timeout-guarded for programs new parser can't handle). """ from .models import BrSeq, BrIf, BrEval, BrPerform, BrSearch, GoTo from .procedure_parser import extract_branch_tree as new_parse, BranchNode def build_branch_tree_fallback(proc_text, fields=None): """New parser primary with old parser timeout fallback.""" from .core import build_branch_tree as old_build # 1. New parser (fast, 10-50ms, no DP cap limit) new_tree, new_assigns = None, {} try: root, assigns_list = new_parse(proc_text, fields) new_tree = _convert_to_model(root) new_assigns = _assigns_list_to_dict(assigns_list) except Exception: pass # New parser generates O(N) paths (not O(2^N)), so no cap needed. # Just use it directly when it works. if new_tree is not None: return new_tree, new_assigns # 2. Old parser with 3s timeout (fallback only) old_tree, old_assigns = None, {} try: import threading r, e, d = [None], [None], [False] def run(): try: r[0] = old_build(proc_text, fields) except Exception as ex: e[0] = ex d[0] = True t = threading.Thread(target=run, daemon=True) t.start(); t.join(3.0) if d[0] and not e[0] and r[0]: ot, oa = r[0] old_tree, old_assigns = ot, oa except Exception: pass if old_tree is not None: return old_tree, old_assigns return BrSeq(), {} def _convert_to_model(root: BranchNode) -> BrSeq: seq = BrSeq() for c in root.children: _convert_node(c, seq) return seq def _convert_node(node: BranchNode, parent: BrSeq): k = node.kind if k in ("PARAGRAPH", "SECTION", "PERFORM_CALL", "GO_TO", "EXIT", "CALL"): for c in node.children: _convert_node(c, parent) return if k == "IF": br = BrIf(node.condition_text or " ".join(node.branch_names)) for c in node.children: if c.kind == "ELSE": for ec in c.children: _convert_node(ec, br.false_seq) elif c.kind == "THEN": for sc in c.children: _convert_node(sc, br.true_seq) else: _convert_node(c, br.true_seq) parent.add(br) return if k == "EVALUATE": subj = (node.branch_names or [""])[0] subj = subj[5:-1] if subj.startswith("EVAL(") and subj.endswith(")") else subj br = BrEval(subj) for c in node.children: if c.kind == "WHEN": cond = (c.branch_names or [""])[0] cond = cond[5:-1] if cond.startswith("WHEN(") and cond.endswith(")") else cond # Strip trailing body text (everything after first COBOL verb) cond = cond.split()[0] if cond.split() else cond ws = BrSeq() for wc in c.children: _convert_node(wc, ws) if cond.upper() == "OTHER": br.has_other = True for wc in c.children: _convert_node(wc, br.other_seq) else: br.when_list.append((cond, ws)) parent.add(br) return if k == "PERFORM": cond = node.condition_text or "" u = cond.upper() if 'VARYING' in u: br = BrPerform("varying", condition=cond) elif 'UNTIL' in u: br = BrPerform("until", condition=cond) else: br = BrPerform("times", condition=cond) for c in node.children: _convert_node(c, br.body_seq) parent.add(br) return if k == "READ": for c in node.children: _convert_node(c, parent) return if k == "AT_END": br = BrIf("AT END") for c in node.children: _convert_node(c, br.true_seq) parent.add(br) return if k == "NOT_AT_END": for i in range(len(parent.children) - 1, -1, -1): if isinstance(parent.children[i], BrIf): for c in node.children: _convert_node(c, parent.children[i].false_seq) break return if k in ("SORT", "MERGE", "WHEN"): name = " ".join(node.branch_names) if node.branch_names else k parent.add(BrPerform("sort", condition=name)) return if k == "GO_TO_DEPENDING": parent.add(GoTo("DEPENDING")) return for c in node.children: _convert_node(c, parent) def _count_br_nodes(node) -> int: count = 0 if isinstance(node, (BrIf, BrEval, BrPerform, BrSearch)): count += 1 if isinstance(node, BrSeq): for c in node.children: count += _count_br_nodes(c) if isinstance(node, BrIf): count += _count_br_nodes(node.true_seq) + _count_br_nodes(node.false_seq) if isinstance(node, BrEval): for _, s in node.when_list: count += _count_br_nodes(s) count += _count_br_nodes(node.other_seq) if isinstance(node, BrPerform): count += _count_br_nodes(node.body_seq) if isinstance(node, BrSearch): count += _count_br_nodes(node.at_end_seq) for _, s in node.when_list: count += _count_br_nodes(s) return count def _assigns_list_to_dict(assigns_list: list) -> dict: result = {} for a in assigns_list: tgt = a.get("tgt", "") src = a.get("src") or a.get("source_vars") if tgt and src: result[tgt] = [a] return result