python: add new module `vdb.printing`
authorJan Vrany <jan.vrany@labware.com>
Mon, 17 May 2021 11:45:10 +0100
changeset 214 8b2af2270dc4
parent 213 9b86866c1b96
child 215 a4a5863bc49b
python: add new module `vdb.printing` ...containing a set of reusable pretty printers and other pretty-printing utilities.
Make.proto
bc.mak
jv_vdbWINrc.rc
python/vdb/__init__.py
python/vdb/printing.py
--- a/Make.proto	Wed Jan 20 08:16:34 2021 +0000
+++ b/Make.proto	Mon May 17 11:45:10 2021 +0100
@@ -63,9 +63,11 @@
 
 all:: preMake classLibRule postMake
 
-pre_objs::  
+pre_objs:: python 
 
 
+python:
+
 $(INSTALLLIB_DIR)/python:
 	mkdir -p $@
 
@@ -206,7 +208,7 @@
 $(OUTDIR)VDBSourceAndDisassemblyApplication.$(O) VDBSourceAndDisassemblyApplication.$(C) VDBSourceAndDisassemblyApplication.$(H): VDBSourceAndDisassemblyApplication.st $(INCLUDE_TOP)/jv/vdb/VDBAbstractApplication.$(H) $(INCLUDE_TOP)/jv/vdb/VDBAbstractContainer.$(H) $(INCLUDE_TOP)/jv/vdb/VDBAbstractContentsApplication.$(H) $(INCLUDE_TOP)/jv/vdb/VDBTabbingContainer.$(H) $(INCLUDE_TOP)/stx/libbasic/Object.$(H) $(INCLUDE_TOP)/stx/libview2/ApplicationModel.$(H) $(INCLUDE_TOP)/stx/libview2/Model.$(H) $(STCHDR)
 $(OUTDIR)VDBStackApplication.$(O) VDBStackApplication.$(C) VDBStackApplication.$(H): VDBStackApplication.st $(INCLUDE_TOP)/jv/vdb/VDBAbstractApplication.$(H) $(INCLUDE_TOP)/jv/vdb/VDBAbstractContentsApplication.$(H) $(INCLUDE_TOP)/jv/vdb/VDBAbstractListApplication.$(H) $(INCLUDE_TOP)/jv/vdb/VDBAbstractTreeApplication.$(H) $(INCLUDE_TOP)/stx/libbasic/Object.$(H) $(INCLUDE_TOP)/stx/libview2/ApplicationModel.$(H) $(INCLUDE_TOP)/stx/libview2/Model.$(H) $(STCHDR)
 $(OUTDIR)VDBVariableObjectListApplication.$(O) VDBVariableObjectListApplication.$(C) VDBVariableObjectListApplication.$(H): VDBVariableObjectListApplication.st $(INCLUDE_TOP)/jv/vdb/VDBAbstractApplication.$(H) $(INCLUDE_TOP)/jv/vdb/VDBAbstractContentsApplication.$(H) $(INCLUDE_TOP)/jv/vdb/VDBAbstractListApplication.$(H) $(INCLUDE_TOP)/jv/vdb/VDBAbstractTreeApplication.$(H) $(INCLUDE_TOP)/stx/libbasic/Object.$(H) $(INCLUDE_TOP)/stx/libview2/ApplicationModel.$(H) $(INCLUDE_TOP)/stx/libview2/Model.$(H) $(STCHDR)
-$(OUTDIR)extensions.$(O): extensions.st $(INCLUDE_TOP)/jv/libgdbs/GDBDebugger.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBDebuggerObject.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBFrame.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBObject.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBVariable.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBVariableObject.$(H) $(INCLUDE_TOP)/stx/libbasic/Object.$(H) $(STCHDR)
+$(OUTDIR)extensions.$(O): extensions.st $(INCLUDE_TOP)/jv/libgdbs/GDBDebugger.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBDebuggerObject.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBFrame.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBObject.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBVariable.$(H) $(INCLUDE_TOP)/jv/libgdbs/GDBVariableObject.$(H) $(INCLUDE_TOP)/stx/libbasic/Object.$(H) $(INCLUDE_TOP)/stx/libview2/Model.$(H) $(STCHDR)
 
 # ENDMAKEDEPEND --- do not remove this line
 
--- a/bc.mak	Wed Jan 20 08:16:34 2021 +0000
+++ b/bc.mak	Mon May 17 11:45:10 2021 +0100
@@ -43,7 +43,7 @@
 
 OBJS= $(COMMON_OBJS) $(WIN32_OBJS)
 
-ALL::  classLibRule
+ALL:: python classLibRule
 
 classLibRule: $(OUTDIR) $(OUTDIR)$(LIBNAME).dll
 
@@ -70,6 +70,8 @@
 
 
 
+python:            
+
 $(INSTALLLIB_DIR)\python:
 	md $@
 
@@ -154,7 +156,7 @@
 $(OUTDIR)VDBSourceAndDisassemblyApplication.$(O) VDBSourceAndDisassemblyApplication.$(C) VDBSourceAndDisassemblyApplication.$(H): VDBSourceAndDisassemblyApplication.st $(INCLUDE_TOP)\jv\vdb\VDBAbstractApplication.$(H) $(INCLUDE_TOP)\jv\vdb\VDBAbstractContainer.$(H) $(INCLUDE_TOP)\jv\vdb\VDBAbstractContentsApplication.$(H) $(INCLUDE_TOP)\jv\vdb\VDBTabbingContainer.$(H) $(INCLUDE_TOP)\stx\libbasic\Object.$(H) $(INCLUDE_TOP)\stx\libview2\ApplicationModel.$(H) $(INCLUDE_TOP)\stx\libview2\Model.$(H) $(STCHDR)
 $(OUTDIR)VDBStackApplication.$(O) VDBStackApplication.$(C) VDBStackApplication.$(H): VDBStackApplication.st $(INCLUDE_TOP)\jv\vdb\VDBAbstractApplication.$(H) $(INCLUDE_TOP)\jv\vdb\VDBAbstractContentsApplication.$(H) $(INCLUDE_TOP)\jv\vdb\VDBAbstractListApplication.$(H) $(INCLUDE_TOP)\jv\vdb\VDBAbstractTreeApplication.$(H) $(INCLUDE_TOP)\stx\libbasic\Object.$(H) $(INCLUDE_TOP)\stx\libview2\ApplicationModel.$(H) $(INCLUDE_TOP)\stx\libview2\Model.$(H) $(STCHDR)
 $(OUTDIR)VDBVariableObjectListApplication.$(O) VDBVariableObjectListApplication.$(C) VDBVariableObjectListApplication.$(H): VDBVariableObjectListApplication.st $(INCLUDE_TOP)\jv\vdb\VDBAbstractApplication.$(H) $(INCLUDE_TOP)\jv\vdb\VDBAbstractContentsApplication.$(H) $(INCLUDE_TOP)\jv\vdb\VDBAbstractListApplication.$(H) $(INCLUDE_TOP)\jv\vdb\VDBAbstractTreeApplication.$(H) $(INCLUDE_TOP)\stx\libbasic\Object.$(H) $(INCLUDE_TOP)\stx\libview2\ApplicationModel.$(H) $(INCLUDE_TOP)\stx\libview2\Model.$(H) $(STCHDR)
-$(OUTDIR)extensions.$(O): extensions.st $(INCLUDE_TOP)\jv\libgdbs\GDBDebugger.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBDebuggerObject.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBFrame.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBObject.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBVariable.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBVariableObject.$(H) $(INCLUDE_TOP)\stx\libbasic\Object.$(H) $(STCHDR)
+$(OUTDIR)extensions.$(O): extensions.st $(INCLUDE_TOP)\jv\libgdbs\GDBDebugger.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBDebuggerObject.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBFrame.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBObject.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBVariable.$(H) $(INCLUDE_TOP)\jv\libgdbs\GDBVariableObject.$(H) $(INCLUDE_TOP)\stx\libbasic\Object.$(H) $(INCLUDE_TOP)\stx\libview2\Model.$(H) $(STCHDR)
 
 # ENDMAKEDEPEND --- do not remove this line
 
--- a/jv_vdbWINrc.rc	Wed Jan 20 08:16:34 2021 +0000
+++ b/jv_vdbWINrc.rc	Mon May 17 11:45:10 2021 +0100
@@ -3,8 +3,8 @@
 // automagically generated from the projectDefinition: jv_vdb.
 //
 VS_VERSION_INFO VERSIONINFO
-  FILEVERSION     6,2,2,2
-  PRODUCTVERSION  6,2,6,0
+  FILEVERSION     8,0,32767,32767
+  PRODUCTVERSION  8,0,99,0
 #if (__BORLANDC__)
   FILEFLAGSMASK   VS_FF_DEBUG | VS_FF_PRERELEASE
   FILEFLAGS       VS_FF_PRERELEASE | VS_FF_SPECIALBUILD
@@ -18,14 +18,14 @@
   BEGIN
     BLOCK "040904E4"
     BEGIN
-      VALUE "CompanyName", "My Company\0"
-      VALUE "FileDescription", "Class Library (LIB)\0"
-      VALUE "FileVersion", "6.2.2.2\0"
+      VALUE "CompanyName", "Jan Vrany\0"
+      VALUE "FileDescription", "Visual / VM Debugger Library (LIB)\0"
+      VALUE "FileVersion", "8.0.32767.32767\0"
       VALUE "InternalName", "jv:vdb\0"
-      VALUE "LegalCopyright", "My CopyRight or CopyLeft\0"
-      VALUE "ProductName", "LibraryName\0"
-      VALUE "ProductVersion", "6.2.6.0\0"
-      VALUE "ProductDate", "Thu, 01 Jun 2017 11:16:29 GMT\0"
+      VALUE "LegalCopyright", "Copyright (C) Jan Vrany 2015-now\0"
+      VALUE "ProductName", "Visual / VM Debugger Library\0"
+      VALUE "ProductVersion", "8.0.99.0\0"
+      VALUE "ProductDate", "Mon, 17 May 2021 10:54:16 GMT\0"
     END
 
   END
--- a/python/vdb/__init__.py	Wed Jan 20 08:16:34 2021 +0000
+++ b/python/vdb/__init__.py	Mon May 17 11:45:10 2021 +0100
@@ -7,18 +7,19 @@
 # You may find a full license text in LICENSE.txt or at http://creativecommons.org/licenses/by-nc/4.0/
 #
 
-import sys
-import itertools
 import argparse
 import copy
+import itertools
+import sys
+
 import gdb
 
 
 class Error(Exception):
     pass
 
+
 class _VarObjRegistry(object):
-
     def __init__(self):
         self._seqno = 0
         self._varobjs = {}
@@ -36,7 +37,7 @@
         """
         Generate and return a new, unique variable name.
         """
-        name = 'vdbvar' + str(self._seqno)
+        name = "vdbvar" + str(self._seqno)
         self._seqno = self._seqno + 1
         return name
 
@@ -59,11 +60,12 @@
     def unregister_all(self):
         self._varobjs = {}
 
+
 _VarObjs = _VarObjRegistry()
 
 
 class _VarObj(object):
-    def __init__(self, name, expr, value, parent = None):
+    def __init__(self, name, expr, value, parent=None):
         self._name = name
         self._expr = expr
         self._value = value
@@ -73,11 +75,25 @@
     def __copy__(self):
         return _VarObj(None, self._expr, self._value, self._parent)
 
-    def to_mi(self, properties = [ 'name', 'exp', 'numchild', 'value', 'type', 'thread_id', 'frozen', 'has_more', 'displayhint', 'dynamic' ]):
+    def to_mi(
+        self,
+        properties=[
+            "name",
+            "exp",
+            "numchild",
+            "value",
+            "type",
+            "thread_id",
+            "frozen",
+            "has_more",
+            "displayhint",
+            "dynamic",
+        ],
+    ):
         """
         Return MI record representing this variable object.
         """
-        return { p : getattr(self, p) for p in properties if getattr(self, p) != None}
+        return {p: getattr(self, p) for p in properties if getattr(self, p) != None}
 
     def children(self):
         """
@@ -85,15 +101,18 @@
         Children are NOT registered, the caller must take care
         about this.
         """
-        if not hasattr(self, '_children'):
+        if not hasattr(self, "_children"):
+
             def child(exp, value):
-                child_name = self._name + '.' + exp
+                child_name = self._name + "." + exp
                 child = _VarObj(child_name, exp, value, self)
                 return child
+
             if not hasattr(self._visualizer, "children"):
                 self._children = []
             else:
-                class lazy():
+
+                class lazy:
                     def __init__(self, iterator):
                         self._head = []
                         self._rest = iterator
@@ -104,10 +123,16 @@
                         for each in self._rest:
                             self._head.append(each)
                             yield each
-                self._children = lazy(map(lambda pair : child(pair[0], pair[1]), self._visualizer.children()))
+
+                self._children = lazy(
+                    map(
+                        lambda pair: child(pair[0], pair[1]),
+                        self._visualizer.children(),
+                    )
+                )
         return iter(self._children)
 
-    def set_visualizer(self,visualizer):
+    def set_visualizer(self, visualizer):
         """
         Set new visualizer
         """
@@ -115,8 +140,6 @@
         self._visualizer = visualizer
         if self._visualizer == None:
             self._visualizer = gdb.default_visualizer(self._value)
-        if self._visualizer == None:
-            self._visualizer = BasicPrinter(self._value)
         if hasattr(self, "_children"):
             del self._children
 
@@ -150,7 +173,10 @@
         aggregate (e.g., a struct), or for a dynamic varobj, this value will not
         be interesting.
         """
-        return str(self._visualizer.to_string())
+        if self._visualizer != None:
+            return str(self._visualizer.to_string())
+        else:
+            return str(self._value)
 
     @property
     def type(self):
@@ -230,13 +256,15 @@
     """
     default_visualizer = gdb.default_visualizer(value)
     if hasattr(default_visualizer, "contents_visualizer"):
-    	return default_visualizer.contents_visualizer(value)
+        return default_visualizer.contents_visualizer(value)
     else:
-    	return default_visualizer
+        return default_visualizer
+
 
 _VarEvaluators = []
 
-def register_evaluator(callable, replace = True):
+
+def register_evaluator(callable, replace=True):
     """
     Register `callable` in list of evaluators which are tried when creating
     -vdb-var-create. To evaluate and expression, `callable` is invoked passing
@@ -244,6 +272,7 @@
     """
     _VarEvaluators.append(callable)
 
+
 def unregister_evaluator(callable):
     """
     Unregister previously registered evaluator.
@@ -251,12 +280,14 @@
     if callable in _VarEvaluators:
         _VarEvaluators.remove(callable)
 
+
 def unregister_evaluators():
     """
     Unregister all evaluators. To be used mainly in tests
     """
     _VarEvaluators = []
 
+
 def parse_and_eval(expression):
     """
     Parse and evaluate expression and return a gdb.Value() as follows:
@@ -294,27 +325,32 @@
     # Nothing works, give up.
     raise Error("Cannot evaluate expression: %s" % expression)
 
+
 def micommand(name):
     def _command(func):
         class _Command(gdb.MICommand):
             def invoke(*args, **kwargs):
                 return func(*args[1:], **kwargs)
+
             def __call__(self, *args, **kwargs):
                 return self.invoke(*args, **kwargs)
+
         return _Command(name)
+
     return _command
 
-@micommand('-vdb-var-create')
+
+@micommand("-vdb-var-create")
 def _var_create(argv):
     argv_parser = argparse.ArgumentParser()
-    argv_parser.add_argument('name')
-    argv_parser.add_argument('frame')
-    argv_parser.add_argument('expr')
+    argv_parser.add_argument("name")
+    argv_parser.add_argument("frame")
+    argv_parser.add_argument("expr")
     args = argv_parser.parse_args(argv)
 
-    if args.frame != '*':
+    if args.frame != "*":
         raise Exception("frame designator must be '*'")
-    if args.name == '-':
+    if args.name == "-":
         args.name = _VarObjs.generate_name()
     _VarObjs.validate_name(args.name)
 
@@ -322,11 +358,12 @@
     _VarObjs.register(varobj)
     return varobj.to_mi()
 
-@micommand('-vdb-var-delete')
+
+@micommand("-vdb-var-delete")
 def _var_delete(argv):
     argv_parser = argparse.ArgumentParser()
-    argv_parser.add_argument('name')
-    argv_parser.add_argument('-c', dest="children_only", action="store_true")
+    argv_parser.add_argument("name")
+    argv_parser.add_argument("-c", dest="children_only", action="store_true")
     args = argv_parser.parse_args(argv)
 
     if args.children_only:
@@ -335,15 +372,17 @@
         _VarObjs.unregister(_VarObjs[args.name])
     return None
 
-@micommand('-vdb-var-update')
+
+@micommand("-vdb-var-update")
 def _var_update(argv):
     # Not yet supported
     return []
 
-@micommand('-vdb-var-duplicate')
+
+@micommand("-vdb-var-duplicate")
 def _var_duplicate(argv):
     argv_parser = argparse.ArgumentParser()
-    argv_parser.add_argument('name')
+    argv_parser.add_argument("name")
     args = argv_parser.parse_args(argv)
 
     orig = _VarObjs[args.name]
@@ -352,12 +391,13 @@
     _VarObjs.register(dup)
     return dup.to_mi()
 
-@micommand('-vdb-var-list-children')
+
+@micommand("-vdb-var-list-children")
 def _var_list_children(argv):
     argv_parser = argparse.ArgumentParser()
-    argv_parser.add_argument('--all-values', dest="values", action="store_true")
-    argv_parser.add_argument('--no-values', dest="values", action="store_false")
-    argv_parser.add_argument('name')
+    argv_parser.add_argument("--all-values", dest="values", action="store_true")
+    argv_parser.add_argument("--no-values", dest="values", action="store_false")
+    argv_parser.add_argument("name")
     args = argv_parser.parse_args(argv)
 
     varobj = _VarObjs[args.name]
@@ -368,23 +408,26 @@
     return map(_VarObj.to_mi, children)
 
 
-@micommand('-vdb-var-info-path-expression')
+@micommand("-vdb-var-info-path-expression")
 def _var_info_path_expression(argv):
     argv_parser = argparse.ArgumentParser()
-    argv_parser.add_argument('name')
+    argv_parser.add_argument("name")
     args = argv_parser.parse_args(argv)
 
     varobj = _VarObjs[args.name]
     if varobj.parent != None:
-        raise Error("-vdb-var-info-path-expression is not supported for children of dynamic varobjs")
+        raise Error(
+            "-vdb-var-info-path-expression is not supported for children of dynamic varobjs"
+        )
     else:
         return varobj.exp
 
-@micommand('-vdb-var-set-visualizer')
+
+@micommand("-vdb-var-set-visualizer")
 def _var_set_visualizer(argv):
     argv_parser = argparse.ArgumentParser()
-    argv_parser.add_argument('name')
-    argv_parser.add_argument('visualizer')
+    argv_parser.add_argument("name")
+    argv_parser.add_argument("visualizer")
     args = argv_parser.parse_args(argv)
 
     varobj = _VarObjs[args.name]
@@ -401,13 +444,15 @@
         visualizer_factory = eval(args.visualizer)
     except:
         import __main__
-        visualizer_factory = eval(args.visualizer, __main__.__dict__ , __main__.__dict__)
+
+        visualizer_factory = eval(args.visualizer, __main__.__dict__, __main__.__dict__)
 
     visualizer = visualizer_factory(varobj._value)
 
     varobj.set_visualizer(visualizer)
 
+
 # Set prompt for both, GDBCLI and Python CLI
-gdb.prompt_hook = lambda x: 'vdb > '
-sys.ps1 = 'vdb pi > '
-sys.ps2 = '       > '
+gdb.prompt_hook = lambda x: "vdb > "
+sys.ps1 = "vdb pi > "
+sys.ps2 = "       > "
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/vdb/printing.py	Mon May 17 11:45:10 2021 +0100
@@ -0,0 +1,113 @@
+#
+# jv:vdb - Visual / VM Debugger
+# Copyright (C) 2015, 2021 Jan Vrany
+#
+# This software is licensed under 'Creative Commons Attribution-NonCommercial 4.0 International License'
+#
+# You may find a full license text in LICENSE.txt or at http://creativecommons.org/licenses/by-nc/4.0/
+#
+
+"""
+Pretty printing utilities
+"""
+import re
+
+import gdb
+import gdb.printing
+
+
+class CxxCollectionPrettyPrinter(gdb.printing.PrettyPrinter):
+    """
+    Class implementing a collection of (sub)pretty-printers specialized
+    for use with C++ class types.
+
+    It behaves much like GDB's RegexpCollectionPrettyPrinter except that it
+    takes C++ inheritance into the account. if there's no pretty-printer
+    registered for given (dynamic) type, it looks if there's a pretty-printer
+    for supertype(s).
+
+    CxxCollectionPrettyPrinter may nest.
+    """
+
+    class CxxSubPrinter(gdb.printing.SubPrettyPrinter):
+        def __init__(self, name, regexp, printer):
+            super().__init__(name)
+            self.regexp = regexp
+            self.regexp_c = re.compile(regexp)
+            self.printer = printer
+
+        def _lookup(self, t):
+            """
+            Return self if self is suitable for given type `t`, `None` otherwise.
+            """
+            if t.code == gdb.TYPE_CODE_PTR:
+                t = t.target()
+            try:
+                if t.name != None and self.regexp_c.search(t.name):
+                    return self
+            except:
+                pass
+            return None
+
+        def __call__(self, val):
+            """
+            Lookup the pretty-printer for the provided value.
+            """
+            return self.printer(val)
+
+    def add_printer(self, name, regexp, printer):
+        """Add a printer to the list.
+
+        Add printer to the list.
+
+        Arguments:
+            name:    name of this pretty printer (for enable/disable)
+            regexp:  the regular expression, as a string.
+            printer: a callable that given a value returns an object to
+                     pretty-print it.
+
+        Returns:
+            Nothing.
+        """
+        if self.subprinters == None:
+            self.subprinters = []
+        for subprinter in self.subprinters:
+            if subprinter.regexp == regexp:
+                subprinter.printer = printer
+                return
+        self.subprinters.append(
+            CxxCollectionPrettyPrinter.CxxSubPrinter(name, regexp, printer)
+        )
+
+    def _lookup(self, t):
+        """
+        Lookup a pretty-printer for given type `t`, traversing supertypes if necessary.
+        Returns the pretty-printer factory or None if no pretty-printer is found.
+        """
+
+        def basetypes(t):
+            if t.code != gdb.TYPE_CODE_STRUCT:
+                return None
+            for f in t.fields():
+                if f.is_base_class:
+                    return f.type
+            return None
+
+        if self.subprinters == None:
+            return None
+        if t.code == gdb.TYPE_CODE_PTR:
+            t = t.target()
+        while t != None:
+            for subprinter in self.subprinters:
+                if subprinter.enabled:
+                    printer = subprinter._lookup(t)
+                    if printer is not None:
+                        return printer
+            t = basetypes(t)
+        return None
+
+    def __call__(self, val):
+        printer = self._lookup(val.dynamic_type)
+        if printer is not None:
+            return printer(val)
+        return None