#! /usr/bin/python3 # Last edited on 2026-11-02 16:02:14 by stolfi import os, sys, re from sys import stderr as err from math import inf, isfinite # import write_parsing_funcs as wpf import bipattern_funcs as bpf import bitemplate_funcs as btf def extract_segments(text0, text1, bimatch): # Given two texts {text0,text1} and a bimatching {bimatch[0..nr-1]} # for them, returns the two macro-parsings {segs0,segs1} of those # texts implied by the bimatching. # # The list {segs0} has {ns=2*nr+1} strings. comprising the {nr} # substrings of {text0} that are defined by the indices {i0} and {j0} # of the rungs (the /hits/) and the substrings before, between,and # after those hits (the /gaps/). The list {segs1} has the {ns} gaps # and hits of {text1} defined by the indices {i1} and {j1} of the # rung. # # The procedre also returns a list {hit_penalties} which are the # {pena} components in rungs {bpvar} fields. debug = False nr = len(bimatch) nh = nr; ng = nh + 1; ns = ng + nh textp = ( text0, text1 ) segs = [ [ ], [ ] ] jtp_prev = [ 0, 0 ] # End or previous hit hit_penalties = [ ] for ir in range(nr): i0, j0, i1, j1, bpvar = bimatch[ir] pena, pat0, pat1 = bpvar if debug: err.write(f"!E {pena = :.8f}\n") assert isinstance(pena, float) and pena >= 0 itp = ( i0, i1 ) jtp = ( j0, j1 ) for side in range(2): jt_prev = jtp_prev[side] it = itp[side] jt = jtp[side] tt = textp[side] nt = len(tt) assert jt_prev <= it <= nt and it <= jt <= nt segs[side].append(tt[jt_prev:it]) # Gap segs[side].append(tt[it:jt]) # Hit hit_penalties.append(pena) jtp_prev = jtp # Last gaps: segs[0].append(text0[jtp_prev[0]:]) segs[1].append(text1[jtp_prev[1]:]) return segs[0], segs[1], hit_penalties # ---------------------------------------------------------------------- def check_bimatch(bimatch, bitemp, text0, text1): debug = False if bimatch == None: return nt = (len(text0), len(text1),) # Lengths of the texts. nh = len(bitemp) ng = nh + 1; ns = ng + nh # Number of hits, gaps, segs. assert len(bimatch) == nh fin0_prev = 0; fin1_prev = 0 for ih in range(nh): ri = bimatch[ih] if debug: err.write(f"!@ checking rung {ih} = {ri = !r}\n") assert ri is not None ini0, fin0, ini1, fin1, bpvar = ri assert fin0_prev <= ini0 and ini0 <= fin0 and fin0 <= nt[0] assert fin1_prev <= ini1 and ini1 <= fin1 and fin1 <= nt[1] pena, pat0, pat1 = bpvar assert isinstance(pena, float) and pena >= 0 assert isinstance(pat0, str) assert isinstance(pat1, str) # Check that {bpvar} is a variant of {bitemp[ih]}: ok = False; for bpvar_chk in bitemp[ih]: pena_chk, pat0_chk, pat1_chk = bpvar_chk ok |= (pat0 == pat0_chk and pat1 == pat1_chk and abs(pena - pena_chk) < 1.0e-6) assert ok, f"no variant of bitemp[{ih}] matches {bpvar = !r}" assert re.fullmatch(pat0, text0[ini0:fin0]) is not None assert re.fullmatch(pat1, text1[ini1:fin1]) is not None fin0_prev = fin0; fin1_prev = fin1 return # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: def write_bimatching(wr, lab, bimatch, text0, pad0, text1, pad1): # Writes out a bimatch {bimatch}. # # The {bimatch} must be a list of rungs {bimatch[0..nr-1]} # as defined above. Uses {text0} and {text1} to extract the # matched strings on the two sides. They are padded with {pad0} and {pad1} # so that the columns align properly. # # Each line of the output is prefixed with the given {lab} string. nr = len(bimatch) # Num of rungs. nt0 = len(text0) nt1 = len(text1) # Find the max width of each keyword: wd0_max = 0 wd1_max = 0 for ir in range(nr): i0, j0, i1, j1, bpvar = bimatch[ir] pena, pat0, pat1 = bpvar assert 0 <= i0 and i0 <= j0 and j0 <= nt0 assert 0 <= i1 and i1 <= j1 and j1 <= nt1 assert isinstance(pena, float) and pena >= 0 assert re.fullmatch(pat0, text0[i0:j0]) is not None assert re.fullmatch(pat1, text1[i1:j1]) is not None wd0_max = max(wd0_max, j0 - i0) wd1_max = max(wd1_max, j1 - i1) # Prints all rungs for ir in range(nr): wr.write(f"{lab}") i0, j0, i1, j1, bpvar = bimatch[ir] pena, pat0, pat1 = bpvar key0 = text0[i0:j0].ljust(wd0_max, pad0) wr.write(f" | {i0:3d} {j0:3d} {key0}") key1 = text1[i1:j1].ljust(wd1_max, pad1) wr.write(f" | {i1:3d} {j1:3d} {key1}") wr.write(f" | ({pat0},{pat1})") wr.write("\n") return # ---------------------------------------------------------------------- def do_tests(): pass return # ---------------------------------------------------------------------- if len(sys.argv) > 1 and sys.argv[1] == "BMF.TEST": do_tests()