1bf215546Sopenharmony_ci#!/usr/bin/env python3 2bf215546Sopenharmony_ci# coding=utf-8 3bf215546Sopenharmony_ci########################################################################## 4bf215546Sopenharmony_ci# 5bf215546Sopenharmony_ci# pytracediff - Compare Gallium XML trace files 6bf215546Sopenharmony_ci# (C) Copyright 2022 Matti 'ccr' Hämäläinen <ccr@tnsp.org> 7bf215546Sopenharmony_ci# 8bf215546Sopenharmony_ci# Permission is hereby granted, free of charge, to any person obtaining a copy 9bf215546Sopenharmony_ci# of this software and associated documentation files (the "Software"), to deal 10bf215546Sopenharmony_ci# in the Software without restriction, including without limitation the rights 11bf215546Sopenharmony_ci# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12bf215546Sopenharmony_ci# copies of the Software, and to permit persons to whom the Software is 13bf215546Sopenharmony_ci# furnished to do so, subject to the following conditions: 14bf215546Sopenharmony_ci# 15bf215546Sopenharmony_ci# The above copyright notice and this permission notice shall be included in 16bf215546Sopenharmony_ci# all copies or substantial portions of the Software. 17bf215546Sopenharmony_ci# 18bf215546Sopenharmony_ci# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19bf215546Sopenharmony_ci# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20bf215546Sopenharmony_ci# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21bf215546Sopenharmony_ci# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22bf215546Sopenharmony_ci# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23bf215546Sopenharmony_ci# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24bf215546Sopenharmony_ci# THE SOFTWARE. 25bf215546Sopenharmony_ci# 26bf215546Sopenharmony_ci########################################################################## 27bf215546Sopenharmony_ci 28bf215546Sopenharmony_cifrom parse import * 29bf215546Sopenharmony_ciimport os 30bf215546Sopenharmony_ciimport sys 31bf215546Sopenharmony_ciimport re 32bf215546Sopenharmony_ciimport signal 33bf215546Sopenharmony_ciimport functools 34bf215546Sopenharmony_ciimport argparse 35bf215546Sopenharmony_ciimport difflib 36bf215546Sopenharmony_ciimport subprocess 37bf215546Sopenharmony_ci 38bf215546Sopenharmony_ciassert sys.version_info >= (3, 6), 'Python >= 3.6 required' 39bf215546Sopenharmony_ci 40bf215546Sopenharmony_ci 41bf215546Sopenharmony_ci### 42bf215546Sopenharmony_ci### ANSI color codes 43bf215546Sopenharmony_ci### 44bf215546Sopenharmony_ciPKK_ANSI_ESC = '\33[' 45bf215546Sopenharmony_ciPKK_ANSI_NORMAL = '0m' 46bf215546Sopenharmony_ciPKK_ANSI_RED = '31m' 47bf215546Sopenharmony_ciPKK_ANSI_GREEN = '32m' 48bf215546Sopenharmony_ciPKK_ANSI_YELLOW = '33m' 49bf215546Sopenharmony_ciPKK_ANSI_PURPLE = '35m' 50bf215546Sopenharmony_ciPKK_ANSI_BOLD = '1m' 51bf215546Sopenharmony_ciPKK_ANSI_ITALIC = '3m' 52bf215546Sopenharmony_ci 53bf215546Sopenharmony_ci 54bf215546Sopenharmony_ci### 55bf215546Sopenharmony_ci### Utility functions and classes 56bf215546Sopenharmony_ci### 57bf215546Sopenharmony_cidef pkk_fatal(msg): 58bf215546Sopenharmony_ci print(f"ERROR: {msg}", file=sys.stderr) 59bf215546Sopenharmony_ci if outpipe is not None: 60bf215546Sopenharmony_ci outpipe.terminate() 61bf215546Sopenharmony_ci sys.exit(1) 62bf215546Sopenharmony_ci 63bf215546Sopenharmony_ci 64bf215546Sopenharmony_cidef pkk_info(msg): 65bf215546Sopenharmony_ci print(msg, file=sys.stderr) 66bf215546Sopenharmony_ci 67bf215546Sopenharmony_ci 68bf215546Sopenharmony_cidef pkk_output(outpipe, msg): 69bf215546Sopenharmony_ci if outpipe is not None: 70bf215546Sopenharmony_ci print(msg, file=outpipe.stdin) 71bf215546Sopenharmony_ci else: 72bf215546Sopenharmony_ci print(msg) 73bf215546Sopenharmony_ci 74bf215546Sopenharmony_ci 75bf215546Sopenharmony_cidef pkk_signal_handler(signal, frame): 76bf215546Sopenharmony_ci print("\nQuitting due to SIGINT / Ctrl+C!") 77bf215546Sopenharmony_ci if outpipe is not None: 78bf215546Sopenharmony_ci outpipe.terminate() 79bf215546Sopenharmony_ci sys.exit(1) 80bf215546Sopenharmony_ci 81bf215546Sopenharmony_ci 82bf215546Sopenharmony_cidef pkk_arg_range(vstr, vmin, vmax): 83bf215546Sopenharmony_ci try: 84bf215546Sopenharmony_ci value = int(vstr) 85bf215546Sopenharmony_ci except Exception as e: 86bf215546Sopenharmony_ci raise argparse.ArgumentTypeError(f"value '{vstr}' is not an integer") 87bf215546Sopenharmony_ci 88bf215546Sopenharmony_ci value = int(vstr) 89bf215546Sopenharmony_ci if value < vmin or value > vmax: 90bf215546Sopenharmony_ci raise argparse.ArgumentTypeError(f"value {value} not in range {vmin}-{vmax}") 91bf215546Sopenharmony_ci else: 92bf215546Sopenharmony_ci return value 93bf215546Sopenharmony_ci 94bf215546Sopenharmony_ci 95bf215546Sopenharmony_ciclass PKKArgumentParser(argparse.ArgumentParser): 96bf215546Sopenharmony_ci def print_help(self): 97bf215546Sopenharmony_ci print("pytracediff - Compare Gallium XML trace files\n" 98bf215546Sopenharmony_ci "(C) Copyright 2022 Matti 'ccr' Hämäläinen <ccr@tnsp.org>\n") 99bf215546Sopenharmony_ci super().print_help() 100bf215546Sopenharmony_ci print("\nList of junk calls:") 101bf215546Sopenharmony_ci for klass, call in sorted(trace_ignore_calls): 102bf215546Sopenharmony_ci print(f" {klass}::{call}") 103bf215546Sopenharmony_ci 104bf215546Sopenharmony_ci def error(self, msg): 105bf215546Sopenharmony_ci self.print_help() 106bf215546Sopenharmony_ci print(f"\nERROR: {msg}", file=sys.stderr) 107bf215546Sopenharmony_ci sys.exit(2) 108bf215546Sopenharmony_ci 109bf215546Sopenharmony_ci 110bf215546Sopenharmony_ciclass PKKTraceParser(TraceParser): 111bf215546Sopenharmony_ci def __init__(self, stream, options, state): 112bf215546Sopenharmony_ci TraceParser.__init__(self, stream, options, state) 113bf215546Sopenharmony_ci self.call_stack = [] 114bf215546Sopenharmony_ci 115bf215546Sopenharmony_ci def handle_call(self, call): 116bf215546Sopenharmony_ci self.call_stack.append(call) 117bf215546Sopenharmony_ci 118bf215546Sopenharmony_ci 119bf215546Sopenharmony_ciclass PKKPrettyPrinter(PrettyPrinter): 120bf215546Sopenharmony_ci def __init__(self, options): 121bf215546Sopenharmony_ci self.options = options 122bf215546Sopenharmony_ci 123bf215546Sopenharmony_ci def entry_start(self, show_args): 124bf215546Sopenharmony_ci self.data = [] 125bf215546Sopenharmony_ci self.line = "" 126bf215546Sopenharmony_ci self.show_args = show_args 127bf215546Sopenharmony_ci 128bf215546Sopenharmony_ci def entry_get(self): 129bf215546Sopenharmony_ci if self.line != "": 130bf215546Sopenharmony_ci self.data.append(self.line) 131bf215546Sopenharmony_ci return self.data 132bf215546Sopenharmony_ci 133bf215546Sopenharmony_ci def text(self, text): 134bf215546Sopenharmony_ci self.line += text 135bf215546Sopenharmony_ci 136bf215546Sopenharmony_ci def literal(self, text): 137bf215546Sopenharmony_ci self.line += text 138bf215546Sopenharmony_ci 139bf215546Sopenharmony_ci def function(self, text): 140bf215546Sopenharmony_ci self.line += text 141bf215546Sopenharmony_ci 142bf215546Sopenharmony_ci def variable(self, text): 143bf215546Sopenharmony_ci self.line += text 144bf215546Sopenharmony_ci 145bf215546Sopenharmony_ci def address(self, text): 146bf215546Sopenharmony_ci self.line += text 147bf215546Sopenharmony_ci 148bf215546Sopenharmony_ci def newline(self): 149bf215546Sopenharmony_ci self.data.append(self.line) 150bf215546Sopenharmony_ci self.line = "" 151bf215546Sopenharmony_ci 152bf215546Sopenharmony_ci 153bf215546Sopenharmony_ci def visit_literal(self, node): 154bf215546Sopenharmony_ci if node.value is None: 155bf215546Sopenharmony_ci self.literal("NULL") 156bf215546Sopenharmony_ci elif isinstance(node.value, str): 157bf215546Sopenharmony_ci self.literal('"' + node.value + '"') 158bf215546Sopenharmony_ci else: 159bf215546Sopenharmony_ci self.literal(repr(node.value)) 160bf215546Sopenharmony_ci 161bf215546Sopenharmony_ci def visit_blob(self, node): 162bf215546Sopenharmony_ci self.address("blob()") 163bf215546Sopenharmony_ci 164bf215546Sopenharmony_ci def visit_named_constant(self, node): 165bf215546Sopenharmony_ci self.literal(node.name) 166bf215546Sopenharmony_ci 167bf215546Sopenharmony_ci def visit_array(self, node): 168bf215546Sopenharmony_ci self.text("{") 169bf215546Sopenharmony_ci sep = "" 170bf215546Sopenharmony_ci for value in node.elements: 171bf215546Sopenharmony_ci self.text(sep) 172bf215546Sopenharmony_ci if sep != "": 173bf215546Sopenharmony_ci self.newline() 174bf215546Sopenharmony_ci value.visit(self) 175bf215546Sopenharmony_ci sep = ", " 176bf215546Sopenharmony_ci self.text("}") 177bf215546Sopenharmony_ci 178bf215546Sopenharmony_ci def visit_struct(self, node): 179bf215546Sopenharmony_ci self.text("{") 180bf215546Sopenharmony_ci sep = "" 181bf215546Sopenharmony_ci for name, value in node.members: 182bf215546Sopenharmony_ci self.text(sep) 183bf215546Sopenharmony_ci if sep != "": 184bf215546Sopenharmony_ci self.newline() 185bf215546Sopenharmony_ci self.variable(name) 186bf215546Sopenharmony_ci self.text(" = ") 187bf215546Sopenharmony_ci value.visit(self) 188bf215546Sopenharmony_ci sep = ", " 189bf215546Sopenharmony_ci self.text("}") 190bf215546Sopenharmony_ci 191bf215546Sopenharmony_ci def visit_pointer(self, node): 192bf215546Sopenharmony_ci if self.options.named_ptrs: 193bf215546Sopenharmony_ci self.address(node.named_address()) 194bf215546Sopenharmony_ci else: 195bf215546Sopenharmony_ci self.address(node.address) 196bf215546Sopenharmony_ci 197bf215546Sopenharmony_ci def visit_call(self, node): 198bf215546Sopenharmony_ci if not self.options.suppress_variants: 199bf215546Sopenharmony_ci self.text(f"[{node.no:8d}] ") 200bf215546Sopenharmony_ci 201bf215546Sopenharmony_ci if node.klass is not None: 202bf215546Sopenharmony_ci self.function(node.klass +"::"+ node.method) 203bf215546Sopenharmony_ci else: 204bf215546Sopenharmony_ci self.function(node.method) 205bf215546Sopenharmony_ci 206bf215546Sopenharmony_ci if not self.options.method_only or self.show_args: 207bf215546Sopenharmony_ci self.text("(") 208bf215546Sopenharmony_ci if len(node.args): 209bf215546Sopenharmony_ci self.newline() 210bf215546Sopenharmony_ci sep = "" 211bf215546Sopenharmony_ci for name, value in node.args: 212bf215546Sopenharmony_ci self.text(sep) 213bf215546Sopenharmony_ci if sep != "": 214bf215546Sopenharmony_ci self.newline() 215bf215546Sopenharmony_ci self.variable(name) 216bf215546Sopenharmony_ci self.text(" = ") 217bf215546Sopenharmony_ci value.visit(self) 218bf215546Sopenharmony_ci sep = ", " 219bf215546Sopenharmony_ci self.newline() 220bf215546Sopenharmony_ci 221bf215546Sopenharmony_ci self.text(")") 222bf215546Sopenharmony_ci 223bf215546Sopenharmony_ci if node.ret is not None: 224bf215546Sopenharmony_ci self.text(" = ") 225bf215546Sopenharmony_ci node.ret.visit(self) 226bf215546Sopenharmony_ci 227bf215546Sopenharmony_ci if not self.options.suppress_variants and node.time is not None: 228bf215546Sopenharmony_ci self.text(" // time ") 229bf215546Sopenharmony_ci node.time.visit(self) 230bf215546Sopenharmony_ci 231bf215546Sopenharmony_ci 232bf215546Sopenharmony_cidef pkk_parse_trace(filename, options, state): 233bf215546Sopenharmony_ci pkk_info(f"Parsing {filename} ...") 234bf215546Sopenharmony_ci try: 235bf215546Sopenharmony_ci if filename.endswith(".gz"): 236bf215546Sopenharmony_ci from gzip import GzipFile 237bf215546Sopenharmony_ci stream = io.TextIOWrapper(GzipFile(filename, "rb")) 238bf215546Sopenharmony_ci elif filename.endswith(".bz2"): 239bf215546Sopenharmony_ci from bz2 import BZ2File 240bf215546Sopenharmony_ci stream = io.TextIOWrapper(BZ2File(filename, "rb")) 241bf215546Sopenharmony_ci else: 242bf215546Sopenharmony_ci stream = open(filename, "rt") 243bf215546Sopenharmony_ci 244bf215546Sopenharmony_ci except OSError as e: 245bf215546Sopenharmony_ci pkk_fatal(str(e)) 246bf215546Sopenharmony_ci 247bf215546Sopenharmony_ci parser = PKKTraceParser(stream, options, state) 248bf215546Sopenharmony_ci parser.parse() 249bf215546Sopenharmony_ci 250bf215546Sopenharmony_ci return parser.call_stack 251bf215546Sopenharmony_ci 252bf215546Sopenharmony_ci 253bf215546Sopenharmony_cidef pkk_get_line(data, nline): 254bf215546Sopenharmony_ci if nline < len(data): 255bf215546Sopenharmony_ci return data[nline] 256bf215546Sopenharmony_ci else: 257bf215546Sopenharmony_ci return None 258bf215546Sopenharmony_ci 259bf215546Sopenharmony_ci 260bf215546Sopenharmony_cidef pkk_format_line(line, indent, width): 261bf215546Sopenharmony_ci if line is not None: 262bf215546Sopenharmony_ci tmp = indent + line 263bf215546Sopenharmony_ci if len(tmp) > width: 264bf215546Sopenharmony_ci return tmp[0:(width - 3)] + "..." 265bf215546Sopenharmony_ci else: 266bf215546Sopenharmony_ci return tmp 267bf215546Sopenharmony_ci else: 268bf215546Sopenharmony_ci return "" 269bf215546Sopenharmony_ci 270bf215546Sopenharmony_ci 271bf215546Sopenharmony_ci### 272bf215546Sopenharmony_ci### Main program starts 273bf215546Sopenharmony_ci### 274bf215546Sopenharmony_ciif __name__ == "__main__": 275bf215546Sopenharmony_ci ### Check if output is a terminal 276bf215546Sopenharmony_ci outpipe = None 277bf215546Sopenharmony_ci redirect = False 278bf215546Sopenharmony_ci 279bf215546Sopenharmony_ci try: 280bf215546Sopenharmony_ci defwidth = os.get_terminal_size().columns 281bf215546Sopenharmony_ci redirect = True 282bf215546Sopenharmony_ci except OSError: 283bf215546Sopenharmony_ci defwidth = 80 284bf215546Sopenharmony_ci 285bf215546Sopenharmony_ci signal.signal(signal.SIGINT, pkk_signal_handler) 286bf215546Sopenharmony_ci 287bf215546Sopenharmony_ci ### Parse arguments 288bf215546Sopenharmony_ci optparser = PKKArgumentParser( 289bf215546Sopenharmony_ci usage="%(prog)s [options] <tracefile #1> <tracefile #2>\n") 290bf215546Sopenharmony_ci 291bf215546Sopenharmony_ci optparser.add_argument("filename1", 292bf215546Sopenharmony_ci type=str, action="store", 293bf215546Sopenharmony_ci metavar="<tracefile #1>", 294bf215546Sopenharmony_ci help="Gallium trace XML filename (plain or .gz, .bz2)") 295bf215546Sopenharmony_ci 296bf215546Sopenharmony_ci optparser.add_argument("filename2", 297bf215546Sopenharmony_ci type=str, action="store", 298bf215546Sopenharmony_ci metavar="<tracefile #2>", 299bf215546Sopenharmony_ci help="Gallium trace XML filename (plain or .gz, .bz2)") 300bf215546Sopenharmony_ci 301bf215546Sopenharmony_ci optparser.add_argument("-p", "--plain", 302bf215546Sopenharmony_ci dest="plain", 303bf215546Sopenharmony_ci action="store_true", 304bf215546Sopenharmony_ci help="disable ANSI color etc. formatting") 305bf215546Sopenharmony_ci 306bf215546Sopenharmony_ci optparser.add_argument("-S", "--sup-variants", 307bf215546Sopenharmony_ci dest="suppress_variants", 308bf215546Sopenharmony_ci action="store_true", 309bf215546Sopenharmony_ci help="suppress some variants in output") 310bf215546Sopenharmony_ci 311bf215546Sopenharmony_ci optparser.add_argument("-C", "--sup-common", 312bf215546Sopenharmony_ci dest="suppress_common", 313bf215546Sopenharmony_ci action="store_true", 314bf215546Sopenharmony_ci help="suppress common sections completely") 315bf215546Sopenharmony_ci 316bf215546Sopenharmony_ci optparser.add_argument("-N", "--named", 317bf215546Sopenharmony_ci dest="named_ptrs", 318bf215546Sopenharmony_ci action="store_true", 319bf215546Sopenharmony_ci help="generate symbolic names for raw pointer values") 320bf215546Sopenharmony_ci 321bf215546Sopenharmony_ci optparser.add_argument("-M", "--method-only", 322bf215546Sopenharmony_ci dest="method_only", 323bf215546Sopenharmony_ci action="store_true", 324bf215546Sopenharmony_ci help="output only call names without arguments") 325bf215546Sopenharmony_ci 326bf215546Sopenharmony_ci optparser.add_argument("-I", "--ignore-junk", 327bf215546Sopenharmony_ci dest="ignore_junk", 328bf215546Sopenharmony_ci action="store_true", 329bf215546Sopenharmony_ci help="filter out/ignore junk calls (see below)") 330bf215546Sopenharmony_ci 331bf215546Sopenharmony_ci optparser.add_argument("-w", "--width", 332bf215546Sopenharmony_ci dest="output_width", 333bf215546Sopenharmony_ci type=functools.partial(pkk_arg_range, vmin=16, vmax=512), default=defwidth, 334bf215546Sopenharmony_ci metavar="N", 335bf215546Sopenharmony_ci help="output width (default: %(default)s)") 336bf215546Sopenharmony_ci 337bf215546Sopenharmony_ci options = optparser.parse_args() 338bf215546Sopenharmony_ci 339bf215546Sopenharmony_ci ### Parse input files 340bf215546Sopenharmony_ci stack1 = pkk_parse_trace(options.filename1, options, TraceStateData()) 341bf215546Sopenharmony_ci stack2 = pkk_parse_trace(options.filename2, options, TraceStateData()) 342bf215546Sopenharmony_ci 343bf215546Sopenharmony_ci ### Perform diffing 344bf215546Sopenharmony_ci pkk_info("Matching trace sequences ...") 345bf215546Sopenharmony_ci sequence = difflib.SequenceMatcher(lambda x : x.is_junk, stack1, stack2, autojunk=False) 346bf215546Sopenharmony_ci 347bf215546Sopenharmony_ci pkk_info("Sequencing diff ...") 348bf215546Sopenharmony_ci opcodes = sequence.get_opcodes() 349bf215546Sopenharmony_ci if len(opcodes) == 1 and opcodes[0][0] == "equal": 350bf215546Sopenharmony_ci print("The files are identical.") 351bf215546Sopenharmony_ci sys.exit(0) 352bf215546Sopenharmony_ci 353bf215546Sopenharmony_ci ### Redirect output to 'less' if stdout is a tty 354bf215546Sopenharmony_ci try: 355bf215546Sopenharmony_ci if redirect: 356bf215546Sopenharmony_ci outpipe = subprocess.Popen(["less", "-S", "-R"], stdin=subprocess.PIPE, encoding="utf8") 357bf215546Sopenharmony_ci 358bf215546Sopenharmony_ci ### Output results 359bf215546Sopenharmony_ci pkk_info("Outputting diff ...") 360bf215546Sopenharmony_ci colwidth = int((options.output_width - 3) / 2) 361bf215546Sopenharmony_ci colfmt = "{}{:"+ str(colwidth) +"s}{} {}{}{} {}{:"+ str(colwidth) +"s}{}" 362bf215546Sopenharmony_ci 363bf215546Sopenharmony_ci printer = PKKPrettyPrinter(options) 364bf215546Sopenharmony_ci 365bf215546Sopenharmony_ci prevtag = "" 366bf215546Sopenharmony_ci for tag, start1, end1, start2, end2 in opcodes: 367bf215546Sopenharmony_ci if tag == "equal": 368bf215546Sopenharmony_ci show_args = False 369bf215546Sopenharmony_ci if options.suppress_common: 370bf215546Sopenharmony_ci if tag != prevtag: 371bf215546Sopenharmony_ci pkk_output(outpipe, "[...]") 372bf215546Sopenharmony_ci continue 373bf215546Sopenharmony_ci 374bf215546Sopenharmony_ci sep = "|" 375bf215546Sopenharmony_ci ansi1 = ansi2 = ansiend = "" 376bf215546Sopenharmony_ci show_args = False 377bf215546Sopenharmony_ci elif tag == "insert": 378bf215546Sopenharmony_ci sep = "+" 379bf215546Sopenharmony_ci ansi1 = "" 380bf215546Sopenharmony_ci ansi2 = PKK_ANSI_ESC + PKK_ANSI_GREEN 381bf215546Sopenharmony_ci show_args = True 382bf215546Sopenharmony_ci elif tag == "delete": 383bf215546Sopenharmony_ci sep = "-" 384bf215546Sopenharmony_ci ansi1 = PKK_ANSI_ESC + PKK_ANSI_RED 385bf215546Sopenharmony_ci ansi2 = "" 386bf215546Sopenharmony_ci show_args = True 387bf215546Sopenharmony_ci elif tag == "replace": 388bf215546Sopenharmony_ci sep = ">" 389bf215546Sopenharmony_ci ansi1 = ansi2 = PKK_ANSI_ESC + PKK_ANSI_BOLD 390bf215546Sopenharmony_ci show_args = True 391bf215546Sopenharmony_ci else: 392bf215546Sopenharmony_ci pkk_fatal(f"Internal error, unsupported difflib.SequenceMatcher operation '{tag}'.") 393bf215546Sopenharmony_ci 394bf215546Sopenharmony_ci # No ANSI, please 395bf215546Sopenharmony_ci if options.plain: 396bf215546Sopenharmony_ci ansi1 = ansisep = ansi2 = ansiend = "" 397bf215546Sopenharmony_ci else: 398bf215546Sopenharmony_ci ansisep = PKK_ANSI_ESC + PKK_ANSI_PURPLE 399bf215546Sopenharmony_ci ansiend = PKK_ANSI_ESC + PKK_ANSI_NORMAL 400bf215546Sopenharmony_ci 401bf215546Sopenharmony_ci 402bf215546Sopenharmony_ci # Print out the block 403bf215546Sopenharmony_ci ncall1 = start1 404bf215546Sopenharmony_ci ncall2 = start2 405bf215546Sopenharmony_ci last1 = last2 = False 406bf215546Sopenharmony_ci while True: 407bf215546Sopenharmony_ci # Get line data 408bf215546Sopenharmony_ci if ncall1 < end1: 409bf215546Sopenharmony_ci if not options.ignore_junk or not stack1[ncall1].is_junk: 410bf215546Sopenharmony_ci printer.entry_start(show_args) 411bf215546Sopenharmony_ci stack1[ncall1].visit(printer) 412bf215546Sopenharmony_ci data1 = printer.entry_get() 413bf215546Sopenharmony_ci else: 414bf215546Sopenharmony_ci data1 = [] 415bf215546Sopenharmony_ci ncall1 += 1 416bf215546Sopenharmony_ci else: 417bf215546Sopenharmony_ci data1 = [] 418bf215546Sopenharmony_ci last1 = True 419bf215546Sopenharmony_ci 420bf215546Sopenharmony_ci if ncall2 < end2: 421bf215546Sopenharmony_ci if not options.ignore_junk or not stack2[ncall2].is_junk: 422bf215546Sopenharmony_ci printer.entry_start(show_args) 423bf215546Sopenharmony_ci stack2[ncall2].visit(printer) 424bf215546Sopenharmony_ci data2 = printer.entry_get() 425bf215546Sopenharmony_ci else: 426bf215546Sopenharmony_ci data2 = [] 427bf215546Sopenharmony_ci ncall2 += 1 428bf215546Sopenharmony_ci else: 429bf215546Sopenharmony_ci data2 = [] 430bf215546Sopenharmony_ci last2 = True 431bf215546Sopenharmony_ci 432bf215546Sopenharmony_ci # Check if we are at last call of both 433bf215546Sopenharmony_ci if last1 and last2: 434bf215546Sopenharmony_ci break 435bf215546Sopenharmony_ci 436bf215546Sopenharmony_ci nline = 0 437bf215546Sopenharmony_ci while nline < len(data1) or nline < len(data2): 438bf215546Sopenharmony_ci # Determine line start indentation 439bf215546Sopenharmony_ci if nline > 0: 440bf215546Sopenharmony_ci if options.suppress_variants: 441bf215546Sopenharmony_ci indent = " "*8 442bf215546Sopenharmony_ci else: 443bf215546Sopenharmony_ci indent = " "*12 444bf215546Sopenharmony_ci else: 445bf215546Sopenharmony_ci indent = "" 446bf215546Sopenharmony_ci 447bf215546Sopenharmony_ci line1 = pkk_get_line(data1, nline) 448bf215546Sopenharmony_ci line2 = pkk_get_line(data2, nline) 449bf215546Sopenharmony_ci 450bf215546Sopenharmony_ci # Highlight differing lines if not plain 451bf215546Sopenharmony_ci if not options.plain and line1 != line2: 452bf215546Sopenharmony_ci if tag == "insert" or tag == "delete": 453bf215546Sopenharmony_ci ansi1 = ansi1 + PKK_ANSI_ESC + PKK_ANSI_BOLD 454bf215546Sopenharmony_ci elif tag == "replace": 455bf215546Sopenharmony_ci ansi1 = ansi2 = ansi1 + PKK_ANSI_ESC + PKK_ANSI_YELLOW 456bf215546Sopenharmony_ci 457bf215546Sopenharmony_ci # Output line 458bf215546Sopenharmony_ci pkk_output(outpipe, colfmt.format( 459bf215546Sopenharmony_ci ansi1, pkk_format_line(line1, indent, colwidth), ansiend, 460bf215546Sopenharmony_ci ansisep, sep, ansiend, 461bf215546Sopenharmony_ci ansi2, pkk_format_line(line2, indent, colwidth), ansiend). 462bf215546Sopenharmony_ci rstrip()) 463bf215546Sopenharmony_ci 464bf215546Sopenharmony_ci nline += 1 465bf215546Sopenharmony_ci 466bf215546Sopenharmony_ci if tag == "equal" and options.suppress_common: 467bf215546Sopenharmony_ci pkk_output(outpipe, "[...]") 468bf215546Sopenharmony_ci 469bf215546Sopenharmony_ci prevtag = tag 470bf215546Sopenharmony_ci 471bf215546Sopenharmony_ci except Exception as e: 472bf215546Sopenharmony_ci pkk_fatal(str(e)) 473bf215546Sopenharmony_ci 474bf215546Sopenharmony_ci if outpipe is not None: 475bf215546Sopenharmony_ci outpipe.communicate() 476