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