[committed] libstdc++: Add GDB printers for <chrono> types

Message ID 20221222233804.772229-1-jwakely@redhat.com
State Repeat Merge
Headers
Series [committed] libstdc++: Add GDB printers for <chrono> types |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

Jonathan Wakely Dec. 22, 2022, 11:38 p.m. UTC
  These should really have tests for the new types, but I've been using
them heavily for a few weeks and they work well. I would rather get them
committed now and add tests later.

Tested x86_64-linux. Pushed to trunk.

-- >8 --

libstdc++-v3/ChangeLog:

	* python/libstdcxx/v6/printers.py (StdChronoDurationPrinter)
	(StdChronoTimePointPrinter, StdChronoZonedTimePrinter)
	(StdChronoCalendarPrinter, StdChronoTimeZonePrinter)
	(StdChronoLeapSecondPrinter, StdChronoTzdbPrinter)
	(StdChronoTimeZoneRulePrinter): New printers.
---
 libstdc++-v3/python/libstdcxx/v6/printers.py | 265 ++++++++++++++++++-
 1 file changed, 261 insertions(+), 4 deletions(-)
  

Comments

Tom Tromey Sept. 27, 2023, 3:37 p.m. UTC | #1
>>>>> Jonathan Wakely via Gcc-patches <gcc-patches@gcc.gnu.org> writes:

Replying to a quite old email...

I ran a Python linter on the libstdc++ pretty-printers.

I have fixes for most of the issues that are worth fixing (I didn't
bother with line lengths -- FWIW in gdb we just run 'black' and don't
worry about these details), but the patch I'm replying to had a problem
that I didn't know how to fix:

> +class StdChronoTimeZoneRulePrinter:
[...]
> +        if kind == 0: # DayOfMonth
> +                start = '{} {}{}'.format(month, ordinal_day)

flake8 points out that this call to format has three placeholders but
only two arguments.

Tom
  
Jonathan Wakely Sept. 27, 2023, 4:15 p.m. UTC | #2
On Wed, 27 Sept 2023 at 16:37, Tom Tromey <tromey@adacore.com> wrote:
>
> >>>>> Jonathan Wakely via Gcc-patches <gcc-patches@gcc.gnu.org> writes:
>
> Replying to a quite old email...
>
> I ran a Python linter on the libstdc++ pretty-printers.
>
> I have fixes for most of the issues that are worth fixing (I didn't
> bother with line lengths -- FWIW in gdb we just run 'black' and don't
> worry about these details),

I used autopep8 and committed the result as
e08559271b2d797f658579ac8610dbf5e58bcfd8 so the line lengths should be
OK now.

> but the patch I'm replying to had a problem
> that I didn't know how to fix:
>
> > +class StdChronoTimeZoneRulePrinter:
> [...]
> > +        if kind == 0: # DayOfMonth
> > +                start = '{} {}{}'.format(month, ordinal_day)
>
> flake8 points out that this call to format has three placeholders but
> only two arguments.

Oops, I think it was originally written like this:

'{} {}{}'.format(month, day, suffixes.get(day, 'th'))

but then I refactored it to:

ordinal_day = '{}{}'.format(day, suffixes.get(day, 'th'))
if kind == 0:  # DayOfMonth
            start = '{} {}{}'.format(month, ordinal_day)

So the fix is to just change the string to '{} {}' which I've pushed
as 1fab05a885a308c19cf42b72fd36805ddf27fdc8 now (also attached).

These printers are for implementation details internal to the library,
which are never exposed to users. I added them because they made it
much easier to debug the implementation when stepping through library
functions, but that means there are no tests for them.

Thanks for finding this!
commit 1fab05a885a308c19cf42b72fd36805ddf27fdc8
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Sep 27 17:03:51 2023

    libstdc++: Fix format string in StdChronoTimeZoneRulePrinter
    
    libstdc++-v3/ChangeLog:
    
            * python/libstdcxx/v6/printers.py (StdChronoTimeZoneRulePrinter):
            Fix incorrect number of replacement fields.

diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py
index c0056de2565..d60c8003a63 100644
--- a/libstdc++-v3/python/libstdcxx/v6/printers.py
+++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
@@ -2215,7 +2215,7 @@ class StdChronoTimeZoneRulePrinter:
         day = on['day_of_month']
         ordinal_day = '{}{}'.format(day, suffixes.get(day, 'th'))
         if kind == 0:  # DayOfMonth
-            start = '{} {}{}'.format(month, ordinal_day)
+            start = '{} {}'.format(month, ordinal_day)
         else:
             weekday = weekdays[on['day_of_week']]
             if kind == 1:  # LastWeekDay
  
Tom Tromey Sept. 27, 2023, 5:24 p.m. UTC | #3
>> I have fixes for most of the issues that are worth fixing (I didn't
>> bother with line lengths -- FWIW in gdb we just run 'black' and don't
>> worry about these details),

Jonathan> I used autopep8 and committed the result as
Jonathan> e08559271b2d797f658579ac8610dbf5e58bcfd8 so the line lengths
Jonathan> should be OK now.

Yeah, my patches are on top of that, but flake8 still complains, and I
still see lines > 79 characters.  However maybe flake8 isn't the checker
you want to use, or maybe you have something set up for a different line
length?

Jonathan> So the fix is to just change the string to '{} {}' which I've pushed
Jonathan> as 1fab05a885a308c19cf42b72fd36805ddf27fdc8 now (also attached).

Thank you.

Tom
  
Jonathan Wakely Sept. 27, 2023, 7:57 p.m. UTC | #4
On Wed, 27 Sept 2023, 18:25 Tom Tromey via Libstdc++, <libstdc++@gcc.gnu.org>
wrote:

> >> I have fixes for most of the issues that are worth fixing (I didn't
> >> bother with line lengths -- FWIW in gdb we just run 'black' and don't
> >> worry about these details),
>
> Jonathan> I used autopep8 and committed the result as
> Jonathan> e08559271b2d797f658579ac8610dbf5e58bcfd8 so the line lengths
> Jonathan> should be OK now.
>
> Yeah, my patches are on top of that, but flake8 still complains, and I
> still see lines > 79 characters.  However maybe flake8 isn't the checker
> you want to use, or maybe you have something set up for a different line
> length?
>

I don't think I have anything set up for python formatting at all, I just
committed whatever autopep8 did with its default settings.

If that's suboptimal, we can consider other tools, if they're reliable and
easy to run.


> Jonathan> So the fix is to just change the string to '{} {}' which I've
> pushed
> Jonathan> as 1fab05a885a308c19cf42b72fd36805ddf27fdc8 now (also attached).
>
> Thank you.
>
> Tom
>
  
Jonathan Wakely Sept. 28, 2023, 7:23 a.m. UTC | #5
On Wed, 27 Sept 2023 at 20:57, Jonathan Wakely <jwakely.gcc@gmail.com>
wrote:
>
>
>
> On Wed, 27 Sept 2023, 18:25 Tom Tromey via Libstdc++, <
libstdc++@gcc.gnu.org> wrote:
>>
>> >> I have fixes for most of the issues that are worth fixing (I didn't
>> >> bother with line lengths -- FWIW in gdb we just run 'black' and don't
>> >> worry about these details),
>>
>> Jonathan> I used autopep8 and committed the result as
>> Jonathan> e08559271b2d797f658579ac8610dbf5e58bcfd8 so the line lengths
>> Jonathan> should be OK now.
>>
>> Yeah, my patches are on top of that, but flake8 still complains, and I
>> still see lines > 79 characters.  However maybe flake8 isn't the checker
>> you want to use, or maybe you have something set up for a different line
>> length?
>
>
> I don't think I have anything set up for python formatting at all, I just
committed whatever autopep8 did with its default settings.

It looks like adding the -a flag would have made more changes.

>
> If that's suboptimal, we can consider other tools, if they're reliable
and easy to run.

The changes made by black seem reasonable, though I prefer it with -S to
disable string-normalization. It also needs an option to use 79 as the
maximum line length.
  
Tom Tromey Sept. 28, 2023, 5:37 p.m. UTC | #6
Jonathan> The changes made by black seem reasonable, though I prefer it
Jonathan> with -S to disable string-normalization. It also needs an
Jonathan> option to use 79 as the maximum line length.

I've got some patches I'm about to send.

I made a pyproject.toml to auto-configure black (and isort), and this
works fine, but it also makes a bunch of edits.  So I'd rather send that
separately, after the current batch of patches is handled.

flake8 still isn't really happy, I guess because there are strings that
cause lines over 79, and black doesn't split those.  But meh, maybe
suppressing some flake8 errors is the way to go.

Tom
  
Jonathan Wakely Sept. 28, 2023, 6:47 p.m. UTC | #7
On Thu, 28 Sept 2023, 18:37 Tom Tromey, <tromey@adacore.com> wrote:

> Jonathan> The changes made by black seem reasonable, though I prefer it
> Jonathan> with -S to disable string-normalization. It also needs an
> Jonathan> option to use 79 as the maximum line length.
>
> I've got some patches I'm about to send.
>
> I made a pyproject.toml to auto-configure black (and isort), and this
> works fine, but it also makes a bunch of edits.  So I'd rather send that
> separately, after the current batch of patches is handled.
>
> flake8 still isn't really happy, I guess because there are strings that
> cause lines over 79, and black doesn't split those.  But meh, maybe
> suppressing some flake8 errors is the way to go.
>


I was about to push some changes to split those strings up.



> Tom
>
  

Patch

diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py
index 1abf0a4bce3..7e694f48f28 100644
--- a/libstdc++-v3/python/libstdcxx/v6/printers.py
+++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
@@ -19,6 +19,7 @@  import gdb
 import itertools
 import re
 import sys, os, errno
+from datetime import datetime, timezone
 
 ### Python 2 + Python 3 compatibility code
 
@@ -1871,6 +1872,239 @@  class StdFormatArgsPrinter:
             size = self.val['_M_unpacked_size']
         return "%s with %d arguments" % (typ, size)
 
+def std_ratio_t_tuple(ratio_type):
+    # TODO use reduced period i.e. duration::period
+    period = self.val.type.template_argument(1)
+    num = period.template_argument(0)
+    den = period.template_argument(1)
+    return (num, den)
+
+class StdChronoDurationPrinter:
+    "Print a std::chrono::duration"
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def _ratio(self):
+        # TODO use reduced period i.e. duration::period
+        period = self.val.type.template_argument(1)
+        num = period.template_argument(0)
+        den = period.template_argument(1)
+        return (num, den)
+
+    def _suffix(self):
+        num, den = self._ratio()
+        if num == 1:
+            if den == 1:
+                return 's'
+            if den == 1000:
+                return 'ms'
+            if den == 1000000:
+                return 'us'
+            if den == 1000000000:
+                return 'ns'
+        elif den == 1:
+            if num == 60:
+                return 'min'
+            if num == 3600:
+                return 'h'
+            if num == 86400:
+                return 'd'
+            return '[{}]s'.format(num)
+        return "[{}/{}]s".format(num, den)
+
+    def to_string(self):
+        return "std::chrono::duration = { %d%s }" % (self.val['__r'], self._suffix())
+
+
+class StdChronoTimePointPrinter:
+    "Print a std::chrono::time_point"
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def _clock(self):
+        clock = self.val.type.template_argument(0)
+        name = strip_versioned_namespace(clock.name)
+        if name == 'std::chrono::_V2::system_clock' \
+                or name == 'std::chrono::system_clock':
+            return ('std::chrono::sys_time', 0)
+        # XXX need to remove leap seconds from utc, gps, and tai
+        #if name == 'std::chrono::utc_clock':
+        #    return ('std::chrono::utc_time', 0)
+        #if name == 'std::chrono::gps_clock':
+        #    return ('std::chrono::gps_clock time_point', 315964809)
+        #if name == 'std::chrono::tai_clock':
+        #    return ('std::chrono::tai_clock time_point', -378691210)
+        if name == 'std::filesystem::__file_clock':
+            return ('std::chrono::file_time', 6437664000)
+        if name == 'std::chrono::local_t':
+            return ('std::chrono::local_time', 0)
+        return ('{} time_point'.format(name), None)
+
+    def to_string(self):
+        clock, offset = self._clock()
+        d = self.val['__d']
+        r = d['__r']
+        printer = StdChronoDurationPrinter(d.type.name, d)
+        suffix = printer._suffix()
+        time = ''
+        if offset is not None:
+            num, den = printer._ratio()
+            secs = (r * num / den) + offset
+            try:
+                dt = datetime.fromtimestamp(secs, timezone.utc)
+                time = ' [{:%Y-%m-%d %H:%M:%S}]'.format(dt)
+            except:
+                pass
+        return '%s = {%d%s%s}' % (clock, r, suffix, time)
+
+class StdChronoZonedTimePrinter:
+    "Print a std::chrono::zoned_time"
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def to_string(self):
+        zone = self.val['_M_zone'].dereference()
+        time = self.val['_M_tp']
+        return 'std::chrono::zoned_time = {{{} {}}}'.format(zone, time)
+
+
+months = [None, 'January', 'February', 'March', 'April', 'May', 'June',
+          'July', 'August', 'September', 'October', 'November', 'December']
+
+weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
+            'Saturday', 'Sunday']
+
+class StdChronoCalendarPrinter:
+    "Print a std::chrono::day, std::chrono::month, std::chrono::year etc."
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def to_string(self):
+        val = self.val
+        typ = self.typename
+        if 'month' in typ and typ != 'std::chrono::year_month_day_last':
+            m = val['_M_m']
+        if typ.startswith('std::chrono::year'):
+            y = val['_M_y']
+
+        if typ == 'std::chrono::day':
+            return '{}'.format(int(val['_M_d']))
+        if typ == 'std::chrono::month':
+            return months[m]
+        if typ == 'std::chrono::year':
+            return '{}y'.format(y)
+        if typ == 'std::chrono::weekday':
+            return '{}'.format(weekdays[val['_M_wd']])
+        if typ == 'std::chrono::weekday_indexed':
+            return '{}[{}]'.format(val['_M_wd'], int(val['_M_index']))
+        if typ == 'std::chrono::weekday_last':
+            return '{}[last]'.format(val['_M_wd'])
+        if typ == 'std::chrono::month_day':
+            return '{}/{}'.format(m, val['_M_d'])
+        if typ == 'std::chrono::month_day_last':
+            return '{}/last'.format(m)
+        if typ == 'std::chrono::month_weekday':
+            return '{}/{}'.format(m, val['_M_wdi'])
+        if typ == 'std::chrono::month_weekday_last':
+            return '{}/{}'.format(m, val['_M_wdl'])
+        if typ == 'std::chrono::year_month':
+            return '{}/{}'.format(y, m)
+        if typ == 'std::chrono::year_month_day':
+            return '{}/{}/{}'.format(y, m, val['_M_d'])
+        if typ == 'std::chrono::year_month_day_last':
+            return '{}/{}'.format(y, val['_M_mdl'])
+        if typ == 'std::chrono::year_month_weekday':
+            return '{}/{}'.format(y, m, val['_M_wdi'])
+        if typ == 'std::chrono::year_month_weekday_last':
+            return '{}/{}'.format(y, m, val['_M_wdl'])
+        if typ.startswith('std::chrono::hh_mm_ss'):
+            fract = ''
+            if val['fractional_width'] != 0:
+                fract = '.{:0{}d}'.format(int(val['_M_ss']['__r']),
+                                          int(val['fractional_width']))
+            h = int(val['_M_h']['__r'])
+            m = int(val['_M_m']['__r'])
+            s = int(val['_M_s']['__r'])
+            if val['_M_is_neg']:
+                h = -h
+            return '{:02}:{:02}:{:02}{}'.format(h, m, s, fract)
+
+class StdChronoTimeZonePrinter:
+    "Print a chrono::time_zone or chrono::time_zone_link"
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def to_string(self):
+        str = '%s %s' % (self.typename, self.val['_M_name'])
+        if self.typename.endswith("_link"):
+            str += ' -> %s' % (self.val['_M_target'])
+        return str
+
+class StdChronoLeapSecondPrinter:
+    "Print a chrono::leap_second"
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def to_string(self):
+        date = self.val['_M_s']['__r']
+        neg = '+-'[date < 0]
+        return '%s %d (%c)' % (self.typename, abs(date), neg)
+
+class StdChronoTzdbPrinter:
+    "Print a chrono::tzdb"
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def to_string(self):
+        return '%s %s' % (self.typename, self.val['version'])
+
+class StdChronoTimeZoneRulePrinter:
+    "Print a chrono::time_zone rule"
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def to_string(self):
+        on = self.val['on']
+        kind = on['kind']
+        month = months[on['month']]
+        suffixes = {1:'st', 2:'nd', 3:'rd', 21:'st', 22:'nd', 23:'rd', 31:'st'}
+        day = on['day_of_month']
+        ordinal_day = '{}{}'.format(day, suffixes.get(day, 'th'))
+        if kind == 0: # DayOfMonth
+                start = '{} {}{}'.format(month, ordinal_day)
+        else:
+            weekday = weekdays[on['day_of_week']]
+            if kind == 1: # LastWeekDay
+                start = 'last {} in {}'.format(weekday, month)
+            else:
+                if kind == 2: # LessEq
+                    direction = ('last', '<=')
+                else:
+                    direction = ('first', '>=')
+                day = on['day_of_month']
+                start = '{} {} {} {} {}'.format(direction[0], weekday,
+                                                direction[1], month,
+                                                ordinal_day)
+        return 'time_zone rule {} from {} to {} starting on {}'.format(
+                self.val['name'], self.val['from'], self.val['to'], start)
+
+
 # A "regular expression" printer which conforms to the
 # "SubPrettyPrinter" protocol from gdb.printing.
 class RxPrinter(object):
@@ -2222,9 +2456,9 @@  def register_type_printers(obj):
     add_one_type_printer(obj, 'fpos', 'streampos')
 
     # Add type printers for <chrono> typedefs.
-    for dur in ('nanoseconds', 'microseconds', 'milliseconds',
-                'seconds', 'minutes', 'hours'):
-        add_one_type_printer(obj, 'duration', dur)
+    for dur in ('nanoseconds', 'microseconds', 'milliseconds', 'seconds',
+                'minutes', 'hours', 'days', 'weeks', 'years', 'months'):
+        add_one_type_printer(obj, 'chrono::duration', 'chrono::' + dur)
 
     # Add type printers for <random> typedefs.
     add_one_type_printer(obj, 'linear_congruential_engine', 'minstd_rand0')
@@ -2376,6 +2610,11 @@  def build_libstdcxx_dictionary ():
         libstdcxx_printer.add_version('std::', 'basic_' + sstream, StdStringStreamPrinter)
         libstdcxx_printer.add_version('std::__cxx11::', 'basic_' + sstream, StdStringStreamPrinter)
 
+    libstdcxx_printer.add_version('std::chrono::', 'duration',
+                                  StdChronoDurationPrinter)
+    libstdcxx_printer.add_version('std::chrono::', 'time_point',
+                                  StdChronoTimePointPrinter)
+
     # std::regex components
     libstdcxx_printer.add_version('std::__detail::', '_State',
                                   StdRegexStatePrinter)
@@ -2428,7 +2667,25 @@  def build_libstdcxx_dictionary ():
     libstdcxx_printer.add_version('std::', 'weak_ordering', StdCmpCatPrinter)
     libstdcxx_printer.add_version('std::', 'strong_ordering', StdCmpCatPrinter)
     libstdcxx_printer.add_version('std::', 'span', StdSpanPrinter)
-    libstdcxx_printer.add_version('std::', 'basic_format_args', StdFormatArgsPrinter)
+    libstdcxx_printer.add_version('std::', 'basic_format_args',
+                                  StdFormatArgsPrinter)
+    for c in ['day','month','year','weekday','weekday_indexed','weekday_last',
+              'month_day','month_day_last','month_weekday','month_weekday_last',
+              'year_month','year_month_day','year_month_day_last',
+              'year_month_weekday','year_month_weekday_last', 'hh_mm_ss']:
+        libstdcxx_printer.add_version('std::chrono::', c,
+                                      StdChronoCalendarPrinter)
+    libstdcxx_printer.add_version('std::chrono::', 'time_zone',
+                                  StdChronoTimeZonePrinter)
+    libstdcxx_printer.add_version('std::chrono::', 'time_zone_link',
+                                  StdChronoTimeZonePrinter)
+    libstdcxx_printer.add_version('std::chrono::', 'zoned_time',
+                                  StdChronoZonedTimePrinter)
+    libstdcxx_printer.add_version('std::chrono::', 'leap_second',
+                                  StdChronoLeapSecondPrinter)
+    libstdcxx_printer.add_version('std::chrono::', 'tzdb', StdChronoTzdbPrinter)
+    #libstdcxx_printer.add_version('std::chrono::(anonymous namespace)', 'Rule',
+    #                              StdChronoTimeZoneRulePrinter)
 
     # Extensions.
     libstdcxx_printer.add_version('__gnu_cxx::', 'slist', StdSlistPrinter)