]> git.cworth.org Git - apitrace/commitdiff
tracediff: Merge tracediff2.py and tracediff.py
authorJosé Fonseca <jose.r.fonseca@gmail.com>
Fri, 16 Nov 2012 22:19:10 +0000 (22:19 +0000)
committerJosé Fonseca <jose.r.fonseca@gmail.com>
Fri, 16 Nov 2012 22:22:01 +0000 (22:22 +0000)
scripts/tracediff.py
scripts/tracediff2.py [deleted file]

index 26395879a10b8c54f3308f6a3a8eeab2e5506414..68d967764d5ab0f1983ddf370d3d85968c74c077 100755 (executable)
 ##########################################################################/
 
 
-import platform
+import difflib
+import itertools
 import optparse
 import os.path
+import platform
 import shutil
 import subprocess
 import sys
 import tempfile
 
 
-class Dumper:
+##########################################################################/
+#
+# Abstract interface
+#
+
+
+class Differ:
+
+    def __init__(self, apitrace):
+        self.apitrace = apitrace
+        self.isatty = sys.stdout.isatty()
+
+    def setRefTrace(self, ref_trace, ref_calls):
+        raise NotImplementedError
+
+    def setSrcTrace(self, src_trace, src_calls):
+        raise NotImplementedError
+
+    def diff(self):
+        raise NotImplementedError
 
-    def __init__(self, trace, calls):
+
+##########################################################################/
+#
+# External diff tool
+#
+
+
+class AsciiDumper:
+
+    def __init__(self, apitrace, trace, calls):
         self.output = tempfile.NamedTemporaryFile()
 
         dump_args = [
-            options.apitrace,
+            apitrace,
             'dump',
             '--color=never',
             '--call-nos=no',
@@ -56,84 +86,370 @@ class Dumper:
         )
 
 
-if platform.system() == 'Windows':
-    start_delete = ''
-    end_delete   = ''
-    start_insert = ''
-    end_insert   = ''
-else:
-    start_delete = '\33[9m\33[31m'
-    end_delete   = '\33[0m'
-    start_insert = '\33[32m'
-    end_insert   = '\33[0m'
-
-
-def diff(ref_trace, src_trace):
-
-    isatty = sys.stdout.isatty()
+class ExternalDiffer(Differ):
 
-    ref_dumper = Dumper(ref_trace, options.ref_calls)
-    src_dumper = Dumper(src_trace, options.src_calls)
-
-    # TODO use difflib instead
-    if options.diff == 'diff':
-        diff_args = [
-                'diff',
+    if platform.system() == 'Windows':
+        start_delete = ''
+        end_delete   = ''
+        start_insert = ''
+        end_insert   = ''
+    else:
+        start_delete = '\33[9m\33[31m'
+        end_delete   = '\33[0m'
+        start_insert = '\33[32m'
+        end_insert   = '\33[0m'
+
+    def __init__(self, apitrace, tool, width=None):
+        Differ.__init__(self, apitrace)
+        self.diff_args = [tool]
+        if tool == 'diff':
+            self.diff_args += [
                 '--speed-large-files',
             ]
-        if isatty:
-            diff_args += [
-                '--old-line-format=' + start_delete + '%l' + end_delete + '\n',
-                '--new-line-format=' + start_insert + '%l' + end_insert + '\n',
-            ]
-    elif options.diff == 'sdiff':
-        diff_args = [
-                'sdiff',
-                '--width=%u' % options.width,
+            if self.isatty:
+                self.diff_args += [
+                    '--old-line-format=' + self.start_delete + '%l' + self.end_delete + '\n',
+                    '--new-line-format=' + self.start_insert + '%l' + self.end_insert + '\n',
+                ]
+        elif tool == 'sdiff':
+            if width is None:
+                import curses
+                curses.setupterm()
+                width = curses.tigetnum('cols')
+            self.diff_args += [
+                '--width=%u' % width,
                 '--speed-large-files',
             ]
-    elif options.diff == 'wdiff':
-        diff_args = [
-                'wdiff',
+        elif tool == 'wdiff':
+            self.diff_args += [
                 #'--terminal',
                 '--avoid-wraps',
             ]
-        if isatty:
-            diff_args += [
-                '--start-delete=' + start_delete,
-                '--end-delete=' + end_delete,
-                '--start-insert=' + start_insert,
-                '--end-insert=' + end_insert,
-            ]
-    else:
-        assert False
-    diff_args += [ref_dumper.output.name, src_dumper.output.name]
+            if self.isatty:
+                self.diff_args += [
+                    '--start-delete=' + self.start_delete,
+                    '--end-delete=' + self.end_delete,
+                    '--start-insert=' + self.start_insert,
+                    '--end-insert=' + self.end_insert,
+                ]
+        else:
+            assert False
+
+    def setRefTrace(self, ref_trace, ref_calls):
+        self.ref_dumper = AsciiDumper(self.apitrace, ref_trace, ref_calls)
+
+    def setSrcTrace(self, src_trace, src_calls):
+        self.src_dumper = AsciiDumper(self.apitrace, src_trace, src_calls)
+
+    def diff(self):
+        diff_args = self.diff_args + [
+            self.ref_dumper.output.name,
+            self.src_dumper.output.name,
+        ]
 
-    ref_dumper.dump.wait()
-    src_dumper.dump.wait()
+        self.ref_dumper.dump.wait()
+        self.src_dumper.dump.wait()
 
-    less = None
-    if isatty:
-        less = subprocess.Popen(
-            args = ['less', '-FRXn'],
-            stdin = subprocess.PIPE
+        less = None
+        if self.isatty:
+            less = subprocess.Popen(
+                args = ['less', '-FRXn'],
+                stdin = subprocess.PIPE
+            )
+
+            diff_stdout = less.stdin
+        else:
+            diff_stdout = None
+
+        diff = subprocess.Popen(
+            args = diff_args,
+            stdout = diff_stdout,
+            universal_newlines = True,
         )
 
-        diff_stdout = less.stdin
-    else:
-        diff_stdout = None
+        diff.wait()
 
-    diff = subprocess.Popen(
-        args = diff_args,
-        stdout = diff_stdout,
-        universal_newlines = True,
-    )
+        if less is not None:
+            less.stdin.close()
+            less.wait()
+
+
+##########################################################################/
+#
+# Python diff
+#
 
-    diff.wait()
+from unpickle import Unpickler, Dumper, Rebuilder
+from highlight import ColorHighlighter, LessHighlighter
 
-    if less is not None:
-        less.stdin.close()
-        less.wait()
+
+ignoredFunctionNames = set([
+    'glGetString',
+    'glXGetClientString',
+    'glXGetCurrentDisplay',
+    'glXGetCurrentContext',
+    'glXGetProcAddress',
+    'glXGetProcAddressARB',
+    'wglGetProcAddress',
+])
+
+
+class Blob:
+    '''Data-less proxy for bytearrays, to save memory.'''
+
+    def __init__(self, size, hash):
+        self.size = size
+        self.hash = hash
+
+    def __repr__(self):
+        return 'blob(%u)' % self.size
+
+    def __eq__(self, other):
+        return isinstance(other, Blob) and self.size == other.size and self.hash == other.hash
+
+    def __hash__(self):
+        return self.hash
+
+
+class BlobReplacer(Rebuilder):
+    '''Replace blobs with proxys.'''
+
+    def visitByteArray(self, obj):
+        return Blob(len(obj), hash(str(obj)))
+
+    def visitCall(self, call):
+        call.args = map(self.visit, call.args)
+        call.ret = self.visit(call.ret)
+
+
+class Loader(Unpickler):
+
+    def __init__(self, stream):
+        Unpickler.__init__(self, stream)
+        self.calls = []
+        self.rebuilder = BlobReplacer()
+
+    def handleCall(self, call):
+        if call.functionName not in ignoredFunctionNames:
+            self.rebuilder.visitCall(call)
+            self.calls.append(call)
+
+
+class PythonDiffer(Differ):
+
+    def __init__(self, apitrace, callNos = False):
+        Differ.__init__(self, apitrace)
+        self.a = None
+        self.b = None
+        if self.isatty:
+            self.highlighter = LessHighlighter()
+        else:
+            self.highlighter = ColorHighlighter()
+        self.delete_color = self.highlighter.red
+        self.insert_color = self.highlighter.green
+        self.callNos = callNos
+        self.aSpace = 0
+        self.bSpace = 0
+        self.dumper = Dumper()
+
+    def setRefTrace(self, ref_trace, ref_calls):
+        self.a = self.readTrace(ref_trace, ref_calls)
+
+    def setSrcTrace(self, src_trace, src_calls):
+        self.b = self.readTrace(src_trace, src_calls)
+
+    def readTrace(self, trace, calls):
+        p = subprocess.Popen(
+            args = [
+                self.apitrace,
+                'pickle',
+                '--symbolic',
+                '--calls=' + calls,
+                trace
+            ],
+            stdout = subprocess.PIPE,
+        )
+
+        parser = Loader(p.stdout)
+        parser.parse()
+        return parser.calls
+
+    def diff(self):
+        try:
+            self._diff()
+        except IOError:
+            pass
+
+    def _diff(self):
+        matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
+        for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
+            if tag == 'replace':
+                self.replace(alo, ahi, blo, bhi)
+            elif tag == 'delete':
+                self.delete(alo, ahi, blo, bhi)
+            elif tag == 'insert':
+                self.insert(alo, ahi, blo, bhi)
+            elif tag == 'equal':
+                self.equal(alo, ahi, blo, bhi)
+            else:
+                raise ValueError, 'unknown tag %s' % (tag,)
+
+    def isjunk(self, call):
+        return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
+
+    def replace(self, alo, ahi, blo, bhi):
+        assert alo < ahi and blo < bhi
+
+        a_names = [call.functionName for call in self.a[alo:ahi]]
+        b_names = [call.functionName for call in self.b[blo:bhi]]
+
+        matcher = difflib.SequenceMatcher(None, a_names, b_names)
+        for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
+            _alo += alo
+            _ahi += alo
+            _blo += blo
+            _bhi += blo
+            if tag == 'replace':
+                self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
+            elif tag == 'delete':
+                self.delete(_alo, _ahi, _blo, _bhi)
+            elif tag == 'insert':
+                self.insert(_alo, _ahi, _blo, _bhi)
+            elif tag == 'equal':
+                self.replace_similar(_alo, _ahi, _blo, _bhi)
+            else:
+                raise ValueError, 'unknown tag %s' % (tag,)
+
+    def replace_similar(self, alo, ahi, blo, bhi):
+        assert alo < ahi and blo < bhi
+        assert ahi - alo == bhi - blo
+        for i in xrange(0, bhi - blo):
+            self.highlighter.write('| ')
+            a_call = self.a[alo + i]
+            b_call = self.b[blo + i]
+            assert a_call.functionName == b_call.functionName
+            self.dumpCallNos(a_call.no, b_call.no)
+            self.highlighter.bold(True)
+            self.highlighter.write(b_call.functionName)
+            self.highlighter.bold(False)
+            self.highlighter.write('(')
+            sep = ''
+            numArgs = max(len(a_call.args), len(b_call.args))
+            for j in xrange(numArgs):
+                self.highlighter.write(sep)
+                try:
+                    a_arg = a_call.args[j]
+                except IndexError:
+                    pass
+                try:
+                    b_arg = b_call.args[j]
+                except IndexError:
+                    pass
+                self.replace_value(a_arg, b_arg)
+                sep = ', '
+            self.highlighter.write(')')
+            if a_call.ret is not None or b_call.ret is not None:
+                self.highlighter.write(' = ')
+                self.replace_value(a_call.ret, b_call.ret)
+            self.highlighter.write('\n')
+
+    def replace_dissimilar(self, alo, ahi, blo, bhi):
+        assert alo < ahi and blo < bhi
+        if bhi - blo < ahi - alo:
+            self.insert(alo, alo, blo, bhi)
+            self.delete(alo, ahi, bhi, bhi)
+        else:
+            self.delete(alo, ahi, blo, blo)
+            self.insert(ahi, ahi, blo, bhi)
+
+    def replace_value(self, a, b):
+        if b == a:
+            self.highlighter.write(self.dumper.visit(b))
+        else:
+            self.highlighter.strike()
+            self.highlighter.color(self.delete_color)
+            self.highlighter.write(self.dumper.visit(a))
+            self.highlighter.normal()
+            self.highlighter.write(" ")
+            self.highlighter.color(self.insert_color)
+            self.highlighter.write(self.dumper.visit(b))
+            self.highlighter.normal()
+
+    escape = "\33["
+
+    def delete(self, alo, ahi, blo, bhi):
+        assert alo < ahi
+        assert blo == bhi
+        for i in xrange(alo, ahi):
+            call = self.a[i]
+            self.highlighter.write('- ')
+            self.dumpCallNos(call.no, None)
+            self.highlighter.strike()
+            self.highlighter.color(self.delete_color)
+            self.dumpCall(call)
+
+    def insert(self, alo, ahi, blo, bhi):
+        assert alo == ahi
+        assert blo < bhi
+        for i in xrange(blo, bhi):
+            call = self.b[i]
+            self.highlighter.write('+ ')
+            self.dumpCallNos(None, call.no)
+            self.highlighter.color(self.insert_color)
+            self.dumpCall(call)
+
+    def equal(self, alo, ahi, blo, bhi):
+        assert alo < ahi and blo < bhi
+        assert ahi - alo == bhi - blo
+        for i in xrange(0, bhi - blo):
+            self.highlighter.write('  ')
+            a_call = self.a[alo + i]
+            b_call = self.b[blo + i]
+            assert a_call.functionName == b_call.functionName
+            assert len(a_call.args) == len(b_call.args)
+            self.dumpCallNos(a_call.no, b_call.no)
+            self.dumpCall(b_call)
+
+    def dumpCallNos(self, aNo, bNo):
+        if not self.callNos:
+            return
+
+        if aNo is None:
+            self.highlighter.write(' '*self.aSpace)
+        else:
+            aNoStr = str(aNo)
+            self.highlighter.strike()
+            self.highlighter.color(self.delete_color)
+            self.highlighter.write(aNoStr)
+            self.highlighter.normal()
+            self.aSpace = len(aNoStr)
+        self.highlighter.write(' ')
+        if bNo is None:
+            self.highlighter.write(' '*self.bSpace)
+        else:
+            bNoStr = str(bNo)
+            self.highlighter.color(self.insert_color)
+            self.highlighter.write(bNoStr)
+            self.highlighter.normal()
+            self.bSpace = len(bNoStr)
+        self.highlighter.write(' ')
+
+    def dumpCall(self, call):
+        self.highlighter.bold(True)
+        self.highlighter.write(call.functionName)
+        self.highlighter.bold(False)
+        self.highlighter.write('(' + ', '.join(itertools.imap(self.dumper.visit, call.args)) + ')')
+        if call.ret is not None:
+            self.highlighter.write(' = ' + self.dumper.visit(call.ret))
+        self.highlighter.normal()
+        self.highlighter.write('\n')
+
+
+
+##########################################################################/
+#
+# Main program
+#
 
 
 def which(executable):
@@ -152,22 +468,13 @@ def which(executable):
     return False
 
 
-def columns():
-    import curses
-    curses.setupterm()
-    return curses.tigetnum('cols')
-
-
 def main():
     '''Main program.
     '''
 
-    # Determine default options
-    default_width = columns()
-
     # Parse command line options
     optparser = optparse.OptionParser(
-        usage='\n\t%prog [options] -- TRACE_FILE TRACE_FILE',
+        usage='\n\t%prog [options] TRACE TRACE',
         version='%%prog')
     optparser.add_option(
         '-a', '--apitrace', metavar='PROGRAM',
@@ -175,12 +482,12 @@ def main():
         help='apitrace command [default: %default]')
     optparser.add_option(
         '-d', '--diff',
-        type="choice", choices=('diff', 'sdiff', 'wdiff'),
+        type="choice", choices=('diff', 'sdiff', 'wdiff', 'python'),
         dest="diff", default=None,
         help="diff program: wdiff, sdiff, or diff [default: auto]")
     optparser.add_option(
         '-c', '--calls', metavar='CALLSET',
-        type="string", dest="calls", default='1-10000',
+        type="string", dest="calls", default='0-10000',
         help="calls to compare [default: %default]")
     optparser.add_option(
         '--ref-calls', metavar='CALLSET',
@@ -190,33 +497,48 @@ def main():
         '--src-calls', metavar='CALLSET',
         type="string", dest="src_calls", default=None,
         help="calls to compare from source trace")
+    optparser.add_option(
+        '--call-nos',
+        action="store_true",
+        dest="call_nos", default=False,
+        help="dump call numbers")
     optparser.add_option(
         '-w', '--width', metavar='NUM',
-        type="int", dest="width", default=default_width,
-        help="columns [default: %default]")
+        type="int", dest="width",
+        help="columns [default: auto]")
 
-    global options
     (options, args) = optparser.parse_args(sys.argv[1:])
     if len(args) != 2:
         optparser.error("incorrect number of arguments")
 
     if options.diff is None:
-        if which('wdiff'):
-            options.diff = 'wdiff'
+        if platform.system() == 'Windows':
+            options.diff = 'python'
         else:
-            sys.stderr.write('warning: wdiff not found\n')
-            if which('sdiff'):
-                options.diff = 'sdiff'
+            if which('wdiff'):
+                options.diff = 'wdiff'
             else:
-                sys.stderr.write('warning: sdiff not found\n')
-                options.diff = 'diff'
+                sys.stderr.write('warning: wdiff not found\n')
+                if which('sdiff'):
+                    options.diff = 'sdiff'
+                else:
+                    sys.stderr.write('warning: sdiff not found\n')
+                    options.diff = 'diff'
 
     if options.ref_calls is None:
         options.ref_calls = options.calls
     if options.src_calls is None:
         options.src_calls = options.calls
 
-    diff(*args)
+    ref_trace, src_trace = args
+
+    if options.diff == 'python':
+        differ = PythonDiffer(options.apitrace, options.call_nos)
+    else:
+        differ = ExternalDiffer(options.apitrace, options.diff, options.width)
+    differ.setRefTrace(ref_trace, options.ref_calls)
+    differ.setSrcTrace(src_trace, options.src_calls)
+    differ.diff()
 
 
 if __name__ == '__main__':
diff --git a/scripts/tracediff2.py b/scripts/tracediff2.py
deleted file mode 100755 (executable)
index 255191e..0000000
+++ /dev/null
@@ -1,341 +0,0 @@
-#!/usr/bin/env python
-##########################################################################
-#
-# Copyright 2011-2012 Jose Fonseca
-# All Rights Reserved.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-##########################################################################/
-
-
-import difflib
-import itertools
-import optparse
-import os.path
-import subprocess
-import sys
-
-from unpickle import Unpickler, Dumper, Rebuilder
-from highlight import ColorHighlighter, LessHighlighter
-
-
-ignoredFunctionNames = set([
-    'glGetString',
-    'glXGetClientString',
-    'glXGetCurrentDisplay',
-    'glXGetCurrentContext',
-    'glXGetProcAddress',
-    'glXGetProcAddressARB',
-    'wglGetProcAddress',
-])
-
-
-class Blob:
-    '''Data-less proxy for bytearrays, to save memory.'''
-
-    def __init__(self, size, hash):
-        self.size = size
-        self.hash = hash
-
-    def __repr__(self):
-        return 'blob(%u)' % self.size
-
-    def __eq__(self, other):
-        return isinstance(other, Blob) and self.size == other.size and self.hash == other.hash
-
-    def __hash__(self):
-        return self.hash
-
-
-class BlobReplacer(Rebuilder):
-    '''Replace blobs with proxys.'''
-
-    def visitByteArray(self, obj):
-        return Blob(len(obj), hash(str(obj)))
-
-    def visitCall(self, call):
-        call.args = map(self.visit, call.args)
-        call.ret = self.visit(call.ret)
-
-
-class Loader(Unpickler):
-
-    def __init__(self, stream):
-        Unpickler.__init__(self, stream)
-        self.calls = []
-        self.rebuilder = BlobReplacer()
-
-    def handleCall(self, call):
-        if call.functionName not in ignoredFunctionNames:
-            self.rebuilder.visitCall(call)
-            self.calls.append(call)
-
-
-def readtrace(trace, calls):
-    p = subprocess.Popen(
-        args = [
-            options.apitrace,
-            'pickle',
-            '--symbolic',
-            '--calls=' + calls,
-            trace
-        ],
-        stdout = subprocess.PIPE,
-    )
-
-    parser = Loader(p.stdout)
-    parser.parse()
-    return parser.calls
-
-
-class SDiffer:
-
-    def __init__(self, a, b, highlighter, callNos = False):
-        self.a = a
-        self.b = b
-        self.highlighter = highlighter
-        self.delete_color = highlighter.red
-        self.insert_color = highlighter.green
-        self.callNos = callNos
-        self.aSpace = 0
-        self.bSpace = 0
-        self.dumper = Dumper()
-
-    def diff(self):
-        matcher = difflib.SequenceMatcher(self.isjunk, self.a, self.b)
-        for tag, alo, ahi, blo, bhi in matcher.get_opcodes():
-            if tag == 'replace':
-                self.replace(alo, ahi, blo, bhi)
-            elif tag == 'delete':
-                self.delete(alo, ahi, blo, bhi)
-            elif tag == 'insert':
-                self.insert(alo, ahi, blo, bhi)
-            elif tag == 'equal':
-                self.equal(alo, ahi, blo, bhi)
-            else:
-                raise ValueError, 'unknown tag %s' % (tag,)
-
-    def isjunk(self, call):
-        return call.functionName == 'glGetError' and call.ret in ('GL_NO_ERROR', 0)
-
-    def replace(self, alo, ahi, blo, bhi):
-        assert alo < ahi and blo < bhi
-        
-        a_names = [call.functionName for call in self.a[alo:ahi]]
-        b_names = [call.functionName for call in self.b[blo:bhi]]
-
-        matcher = difflib.SequenceMatcher(None, a_names, b_names)
-        for tag, _alo, _ahi, _blo, _bhi in matcher.get_opcodes():
-            _alo += alo
-            _ahi += alo
-            _blo += blo
-            _bhi += blo
-            if tag == 'replace':
-                self.replace_dissimilar(_alo, _ahi, _blo, _bhi)
-            elif tag == 'delete':
-                self.delete(_alo, _ahi, _blo, _bhi)
-            elif tag == 'insert':
-                self.insert(_alo, _ahi, _blo, _bhi)
-            elif tag == 'equal':
-                self.replace_similar(_alo, _ahi, _blo, _bhi)
-            else:
-                raise ValueError, 'unknown tag %s' % (tag,)
-
-    def replace_similar(self, alo, ahi, blo, bhi):
-        assert alo < ahi and blo < bhi
-        assert ahi - alo == bhi - blo
-        for i in xrange(0, bhi - blo):
-            self.highlighter.write('| ')
-            a_call = self.a[alo + i]
-            b_call = self.b[blo + i]
-            assert a_call.functionName == b_call.functionName
-            self.dumpCallNos(a_call.no, b_call.no)
-            self.highlighter.bold(True)
-            self.highlighter.write(b_call.functionName)
-            self.highlighter.bold(False)
-            self.highlighter.write('(')
-            sep = ''
-            numArgs = max(len(a_call.args), len(b_call.args))
-            for j in xrange(numArgs):
-                self.highlighter.write(sep)
-                try:
-                    a_arg = a_call.args[j]
-                except IndexError:
-                    pass
-                try:
-                    b_arg = b_call.args[j]
-                except IndexError:
-                    pass
-                self.replace_value(a_arg, b_arg)
-                sep = ', '
-            self.highlighter.write(')')
-            if a_call.ret is not None or b_call.ret is not None:
-                self.highlighter.write(' = ')
-                self.replace_value(a_call.ret, b_call.ret)
-            self.highlighter.write('\n')
-
-    def replace_dissimilar(self, alo, ahi, blo, bhi):
-        assert alo < ahi and blo < bhi
-        if bhi - blo < ahi - alo:
-            self.insert(alo, alo, blo, bhi)
-            self.delete(alo, ahi, bhi, bhi)
-        else:
-            self.delete(alo, ahi, blo, blo)
-            self.insert(ahi, ahi, blo, bhi)
-
-    def replace_value(self, a, b):
-        if b == a:
-            self.highlighter.write(self.dumper.visit(b))
-        else:
-            self.highlighter.strike()
-            self.highlighter.color(self.delete_color)
-            self.highlighter.write(self.dumper.visit(a))
-            self.highlighter.normal()
-            self.highlighter.write(" ")
-            self.highlighter.color(self.insert_color)
-            self.highlighter.write(self.dumper.visit(b))
-            self.highlighter.normal()
-
-    escape = "\33["
-
-    def delete(self, alo, ahi, blo, bhi):
-        assert alo < ahi
-        assert blo == bhi
-        for i in xrange(alo, ahi):
-            call = self.a[i]
-            self.highlighter.write('- ')
-            self.dumpCallNos(call.no, None)
-            self.highlighter.strike()
-            self.highlighter.color(self.delete_color)
-            self.dumpCall(call)
-
-    def insert(self, alo, ahi, blo, bhi):
-        assert alo == ahi
-        assert blo < bhi
-        for i in xrange(blo, bhi):
-            call = self.b[i]
-            self.highlighter.write('+ ')
-            self.dumpCallNos(None, call.no)
-            self.highlighter.color(self.insert_color)
-            self.dumpCall(call)
-
-    def equal(self, alo, ahi, blo, bhi):
-        assert alo < ahi and blo < bhi
-        assert ahi - alo == bhi - blo
-        for i in xrange(0, bhi - blo):
-            self.highlighter.write('  ')
-            a_call = self.a[alo + i]
-            b_call = self.b[blo + i]
-            assert a_call.functionName == b_call.functionName
-            assert len(a_call.args) == len(b_call.args)
-            self.dumpCallNos(a_call.no, b_call.no)
-            self.dumpCall(b_call)
-
-    def dumpCallNos(self, aNo, bNo):
-        if not self.callNos:
-            return
-
-        if aNo is None:
-            self.highlighter.write(' '*self.aSpace)
-        else:
-            aNoStr = str(aNo)
-            self.highlighter.strike()
-            self.highlighter.color(self.delete_color)
-            self.highlighter.write(aNoStr)
-            self.highlighter.normal()
-            self.aSpace = len(aNoStr)
-        self.highlighter.write(' ')
-        if bNo is None:
-            self.highlighter.write(' '*self.bSpace)
-        else:
-            bNoStr = str(bNo)
-            self.highlighter.color(self.insert_color)
-            self.highlighter.write(bNoStr)
-            self.highlighter.normal()
-            self.bSpace = len(bNoStr)
-        self.highlighter.write(' ')
-
-    def dumpCall(self, call):
-        self.highlighter.bold(True)
-        self.highlighter.write(call.functionName)
-        self.highlighter.bold(False)
-        self.highlighter.write('(' + ', '.join(itertools.imap(self.dumper.visit, call.args)) + ')')
-        if call.ret is not None:
-            self.highlighter.write(' = ' + self.dumper.visit(call.ret))
-        self.highlighter.normal()
-        self.highlighter.write('\n')
-
-
-def main():
-    '''Main program.
-    '''
-
-    # Parse command line options
-    optparser = optparse.OptionParser(
-        usage='\n\t%prog <trace> <trace>',
-        version='%%prog')
-    optparser.add_option(
-        '-a', '--apitrace', metavar='PROGRAM',
-        type='string', dest='apitrace', default='apitrace',
-        help='apitrace command [default: %default]')
-    optparser.add_option(
-        '-c', '--calls', metavar='CALLSET',
-        type="string", dest="calls", default='*',
-        help="calls to compare [default: %default]")
-    optparser.add_option(
-        '--ref-calls', metavar='CALLSET',
-        type="string", dest="ref_calls", default=None,
-        help="calls to compare from reference trace")
-    optparser.add_option(
-        '--src-calls', metavar='CALLSET',
-        type="string", dest="src_calls", default=None,
-        help="calls to compare from source trace")
-    optparser.add_option(
-        '--call-nos',
-        action="store_true",
-        dest="call_nos", default=False,
-        help="dump call numbers")
-    global options
-    (options, args) = optparser.parse_args(sys.argv[1:])
-    if len(args) != 2:
-        optparser.error("incorrect number of arguments")
-
-    if options.ref_calls is None:
-        options.ref_calls = options.calls
-    if options.src_calls is None:
-        options.src_calls = options.calls
-
-    ref_calls = readtrace(args[0], options.ref_calls)
-    src_calls = readtrace(args[1], options.src_calls)
-
-    if sys.stdout.isatty():
-        highlighter = LessHighlighter()
-    else:
-        highlighter = ColorHighlighter()
-
-    differ = SDiffer(ref_calls, src_calls, highlighter, options.call_nos)
-    try:
-        differ.diff()
-    except IOError:
-        pass
-
-
-if __name__ == '__main__':
-    main()