Skip to content

Commit e5fe052

Browse files
authored
Merge pull request #1279 from cppalliance/pretty_printers
Add LLDB pretty printers and doc page on how to use them
2 parents 07a7cdf + a7b4eb4 commit e5fe052

File tree

10 files changed

+577
-0
lines changed

10 files changed

+577
-0
lines changed
121 KB
Loading
133 KB
Loading

doc/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* xref:financial_examples.adoc[]
1616
** xref:financial_examples.adoc#examples_money_parsing[Parsing Pricing Data from File]
1717
** xref:financial_examples.adoc#examples_boost_math[Boost.Math Integration]
18+
* xref:debugger.adoc[]
1819
* xref:api_reference.adoc[]
1920
** xref:api_reference.adoc#api_ref_types[Types]
2021
** xref:api_reference.adoc#api_ref_structs[Structs]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
////
2+
Copyright 2025 Matt Borland
3+
Distributed under the Boost Software License, Version 1.0.
4+
https://www.boost.org/LICENSE_1_0.txt
5+
////
6+
7+
[#debug]
8+
= Debugger Support
9+
:idprefix: debug_
10+
11+
Currently, Boost.Decimal supports pretty printing with LLDB.
12+
To load the pretty printer, add the following line to your `.lldbinit`:
13+
14+
[source]
15+
----
16+
command script import /path/to/decimal/extra/decimal_printer.py
17+
----
18+
19+
Once you have loaded `decimal_printer.py` you can run the following example to see how different values are represented with the pretty printer.
20+
21+
.The following example can be run with the debugger to show a variety of values
22+
====
23+
24+
[source,c++]
25+
----
26+
include::example$debugger.cpp[]
27+
----
28+
29+
*_Expected Output for T = decimal32_t:_*
30+
31+
image::dec32_debug.png[]
32+
33+
*_Expected Output for T = decimal_fast32_t:_*
34+
35+
image::dec32_fast_debug.png[]
36+
====
37+
38+
The reason for the difference in how `decimal32_t` and `decimal_fast32_t` is displayed is due to xref:cohorts.adoc[Cohorts and Normalization]
39+
40+
. `decimal32_t`, `decimal64_t` and `decimal128_t` debuggers are cohort preserving
41+
. `decimal_fast32_t`, `decimal_fast64_t` and `decimal_fast128_t` debuggers are always normalized

examples/debugger.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2025 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
//
5+
// This example, when run with the pretty printers, shows how various values are represented
6+
7+
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
8+
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
9+
#include <boost/decimal/decimal128_t.hpp> // For type decimal128_t
10+
#include <boost/decimal/decimal_fast32_t.hpp> // For type decimal_fast32_t
11+
#include <boost/decimal/cmath.hpp> // For nan function to write payload to nans
12+
#include <limits>
13+
14+
template <typename T>
15+
void debug_values()
16+
{
17+
// Displays the maximum and minimum values that the type can hold
18+
// from numeric_limits
19+
const T max {std::numeric_limits<T>::max()};
20+
const T min {std::numeric_limits<T>::min()};
21+
22+
// A number whose representation will change based on IEEE vs fast type
23+
// In the IEEE case 3.140e+00 will be displayed as the pretty printer is cohort preserving
24+
const T short_num {"3.140"};
25+
26+
// Shows how infinities will be displayed
27+
const T pos_inf {std::numeric_limits<T>::infinity()};
28+
const T neg_inf {-std::numeric_limits<T>::infinity()};
29+
30+
// Shows how the different kinds of NANs will be displayed
31+
const T qnan {std::numeric_limits<T>::quiet_NaN()};
32+
const T snan {std::numeric_limits<T>::signaling_NaN()};
33+
34+
// Shows how a payload added to a QNAN will be displayed
35+
const T payload_nan {boost::decimal::nan<T>("7")};
36+
37+
// Break Here:
38+
static_cast<void>(max);
39+
static_cast<void>(min);
40+
static_cast<void>(short_num);
41+
static_cast<void>(pos_inf);
42+
static_cast<void>(neg_inf);
43+
static_cast<void>(qnan);
44+
static_cast<void>(snan);
45+
static_cast<void>(payload_nan);
46+
}
47+
48+
int main()
49+
{
50+
debug_values<boost::decimal::decimal32_t>();
51+
debug_values<boost::decimal::decimal64_t>();
52+
debug_values<boost::decimal::decimal128_t>();
53+
54+
debug_values<boost::decimal::decimal_fast32_t>();
55+
debug_values<boost::decimal::decimal_fast64_t>();
56+
debug_values<boost::decimal::decimal_fast128_t>();
57+
58+
return 0;
59+
}

extra/decimal_printer.py

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Copyright 2025 Matt Borland
2+
# Distributed under the Boost Software License, Version 1.0.
3+
# https://www.boost.org/LICENSE_1_0.txt
4+
5+
from detail.decode_ieee_type import decode_decimal32
6+
from detail.decode_ieee_type import decode_decimal64
7+
from detail.decode_ieee_type import decode_decimal128
8+
from detail.decode_fast_type import decode_decimal_fast32
9+
from detail.decode_fast_type import decode_decimal_fast64
10+
from detail.decode_fast_type import decode_decimal_fast128
11+
12+
import lldb
13+
14+
def decimal32_summary(valobj, internal_dict):
15+
"""
16+
Custom summary for decimal32_t type
17+
Displays in scientific notation with cohort preservation
18+
"""
19+
20+
try:
21+
val = valobj.GetNonSyntheticValue()
22+
bits = val.GetChildMemberWithName("bits_").GetValueAsUnsigned()
23+
return decode_decimal32(bits)
24+
25+
except Exception as e:
26+
return f"<invalid decimal32_t: {e}>"
27+
28+
def decimal64_summary(valobj, internal_dict):
29+
"""
30+
Custom summary for decimal64_t type
31+
Displays in scientific notation with cohort preservation
32+
"""
33+
34+
try:
35+
val = valobj.GetNonSyntheticValue()
36+
bits = val.GetChildMemberWithName("bits_").GetValueAsUnsigned()
37+
return decode_decimal64(bits)
38+
39+
except Exception as e:
40+
return f"<invalid decimal64_t: {e}>"
41+
42+
def decimal128_summary(valobj, internal_dict):
43+
"""
44+
Custom summary for decimal128_t type
45+
Displays in scientific notation with cohort preservation
46+
"""
47+
48+
try:
49+
val = valobj.GetNonSyntheticValue()
50+
bits = val.GetChildMemberWithName("bits_")
51+
bits_high = bits.GetChildMemberWithName("high").GetValueAsUnsigned()
52+
bits_low = bits.GetChildMemberWithName("low").GetValueAsUnsigned()
53+
combined_bits = (bits_high << 64) | bits_low
54+
return decode_decimal128(combined_bits)
55+
56+
except Exception as e:
57+
return f"<invalid decimal128_t: {e}>"
58+
59+
def decimal_fast32_summary(valobj, internal_dict):
60+
"""
61+
Custom summary for decimal_fast32_t type
62+
Displays in scientific notation
63+
"""
64+
65+
try:
66+
val = valobj.GetNonSyntheticValue()
67+
significand = val.GetChildMemberWithName("significand_").GetValueAsUnsigned()
68+
exp = val.GetChildMemberWithName("exponent_").GetValueAsUnsigned()
69+
sign = val.GetChildMemberWithName("sign_").GetValueAsUnsigned()
70+
return decode_decimal_fast32(significand, exp, sign)
71+
72+
except Exception as e:
73+
return f"<invalid decimal_fast32_t: {e}>"
74+
75+
def decimal_fast64_summary(valobj, internal_dict):
76+
"""
77+
Custom summary for decimal_fast64_t type
78+
Displays in scientific notation
79+
"""
80+
81+
try:
82+
val = valobj.GetNonSyntheticValue()
83+
significand = val.GetChildMemberWithName("significand_").GetValueAsUnsigned()
84+
exp = val.GetChildMemberWithName("exponent_").GetValueAsUnsigned()
85+
sign = val.GetChildMemberWithName("sign_").GetValueAsUnsigned()
86+
return decode_decimal_fast64(significand, exp, sign)
87+
88+
except Exception as e:
89+
return f"<invalid decimal_fast64_t: {e}>"
90+
def decimal_fast128_summary(valobj, internal_dict):
91+
"""
92+
Custom summary for decimal_fast128_t type
93+
Displays in scientific notation
94+
"""
95+
96+
try:
97+
val = valobj.GetNonSyntheticValue()
98+
99+
significand = val.GetChildMemberWithName("significand_")
100+
bits_high = significand.GetChildMemberWithName("high").GetValueAsUnsigned()
101+
bits_low = significand.GetChildMemberWithName("low").GetValueAsUnsigned()
102+
combined_bits = (bits_high << 64) | bits_low
103+
104+
exp = val.GetChildMemberWithName("exponent_").GetValueAsUnsigned()
105+
sign = val.GetChildMemberWithName("sign_").GetValueAsUnsigned()
106+
107+
return decode_decimal_fast128(combined_bits, exp, sign)
108+
109+
except Exception as e:
110+
return f"<invalid decimal_fast128_t: {e}>"
111+
112+
def __lldb_init_module(debugger, internal_dict):
113+
decimal32_pattern = r"^(const )?(boost::decimal::decimal32_t|(\w+::)*decimal32_t)( &| \*)?$"
114+
decimal64_pattern = r"^(const )?(boost::decimal::decimal64_t|(\w+::)*decimal64_t)( &| \*)?$"
115+
decimal128_pattern = r"^(const )?(boost::decimal::decimal128_t|(\w+::)*decimal128_t)( &| \*)?$"
116+
117+
decimal_fast32_pattern = r"^(const )?(boost::decimal::decimal_fast32_t|(\w+::)*decimal_fast32_t)( &| \*)?$"
118+
decimal_fast64_pattern = r"^(const )?(boost::decimal::decimal_fast64_t|(\w+::)*decimal_fast64_t)( &| \*)?$"
119+
decimal_fast128_pattern = r"^(const )?(boost::decimal::decimal_fast128_t|(\w+::)*decimal_fast128_t)( &| \*)?$"
120+
121+
debugger.HandleCommand(
122+
f'type summary add -x "{decimal32_pattern}" -e -F decimal_printer.decimal32_summary'
123+
)
124+
debugger.HandleCommand(
125+
f'type synthetic add -x "{decimal32_pattern}" -l decimal_printer.DecimalSyntheticProvider'
126+
)
127+
128+
print("decimal32_t printer loaded successfully")
129+
130+
debugger.HandleCommand(
131+
f'type summary add -x "{decimal64_pattern}" -e -F decimal_printer.decimal64_summary'
132+
)
133+
debugger.HandleCommand(
134+
f'type synthetic add -x "{decimal64_pattern}" -l decimal_printer.DecimalSyntheticProvider'
135+
)
136+
137+
print("decimal64_t printer loaded successfully")
138+
139+
debugger.HandleCommand(
140+
f'type summary add -x "{decimal128_pattern}" -e -F decimal_printer.decimal128_summary'
141+
)
142+
debugger.HandleCommand(
143+
f'type synthetic add -x "{decimal128_pattern}" -l decimal_printer.DecimalSyntheticProvider'
144+
)
145+
146+
print("decimal128_t printer loaded successfully")
147+
148+
debugger.HandleCommand(
149+
f'type summary add -x "{decimal_fast32_pattern}" -e -F decimal_printer.decimal_fast32_summary'
150+
)
151+
debugger.HandleCommand(
152+
f'type synthetic add -x "{decimal_fast32_pattern}" -l decimal_printer.DecimalFastSyntheticProvider'
153+
)
154+
155+
print("decimal_fast32_t printer loaded successfully")
156+
157+
debugger.HandleCommand(
158+
f'type summary add -x "{decimal_fast64_pattern}" -e -F decimal_printer.decimal_fast64_summary'
159+
)
160+
debugger.HandleCommand(
161+
f'type synthetic add -x "{decimal_fast64_pattern}" -l decimal_printer.DecimalFastSyntheticProvider'
162+
)
163+
164+
print("decimal_fast64_t printer loaded successfully")
165+
166+
debugger.HandleCommand(
167+
f'type summary add -x "{decimal_fast128_pattern}" -e -F decimal_printer.decimal_fast128_summary'
168+
)
169+
debugger.HandleCommand(
170+
f'type synthetic add -x "{decimal_fast128_pattern}" -l decimal_printer.DecimalFastSyntheticProvider'
171+
)
172+
173+
print("decimal_fast128_t printer loaded successfully")
174+
175+
class DecimalSyntheticProvider:
176+
def __init__(self, valobj, internal_dict):
177+
self.valobj = valobj
178+
179+
def num_children(self):
180+
return 1
181+
182+
def get_child_index(self, name):
183+
if name == "bits_":
184+
return 0
185+
return -1
186+
187+
def get_child_at_index(self, index):
188+
if index == 0:
189+
return self.valobj.GetChildMemberWithName("bits_")
190+
return None
191+
192+
def update(self):
193+
pass
194+
195+
def has_children(self):
196+
return True
197+
198+
class DecimalFastSyntheticProvider:
199+
def __init__(self, valobj, internal_dict):
200+
self.valobj = valobj
201+
202+
def num_children(self):
203+
return 3
204+
205+
def get_child_index(self, name):
206+
if name == "significand_":
207+
return 0
208+
elif name == "exponent_":
209+
return 1
210+
elif name == "sign_":
211+
return 2
212+
else:
213+
return -1
214+
215+
def get_child_at_index(self, index):
216+
if index == 0:
217+
return self.valobj.GetChildMemberWithName("significand_")
218+
elif index == 1:
219+
return self.valobj.GetChildMemberWithName("exponent_")
220+
elif index == 2:
221+
return self.valobj.GetChildMemberWithName("sign_")
222+
else:
223+
return None
224+
225+
def update(self):
226+
pass
227+
228+
def has_children(self):
229+
return True
230+

0 commit comments

Comments
 (0)