"""LLDB summaries for Rust's `fixed` crate. Load this file from LLDB with: command script import /path/to/shinespark/tools/lldb_fixed.py The implementation does not use floating-point arithmetic, so displayed values are exact even for FixedI128/FixedU128 values. """ import re try: import lldb except ImportError: # Keep the pure parsing and formatting helpers testable without LLDB. lldb = None _FIXED_TYPE_RE = re.compile(r"(?:^|::)Fixed([IU])(8|16|32|64|128)<(.+)>$") _DIRECT_TYPENUM_RE = re.compile(r"(?:^|::)U(\d+)$") _TYPENUM_BIT_RE = re.compile(r"(?:^|::)B([01])(?=\s*[,>])") def parse_fixed_type(type_name): """Return (signed, storage_bits, fractional_bits), or None.""" if not type_name: return None match = _FIXED_TYPE_RE.search(type_name.replace(" ", "")) if match is None: return None signed = match.group(1) == "I" storage_bits = int(match.group(2)) fractional_type = match.group(3) direct = _DIRECT_TYPENUM_RE.search(fractional_type) if direct is not None: fractional_bits = int(direct.group(1)) else: # typenum represents UInt as a binary tree. Its textual Rust # type lists the bits from most significant to least significant. digits = _TYPENUM_BIT_RE.findall(fractional_type) if not digits: return None fractional_bits = int("".join(digits), 2) if fractional_bits > storage_bits: return None return signed, storage_bits, fractional_bits def normalize_bits(raw_bits, signed, storage_bits): """Interpret a possibly unsigned LLDB integer using the fixed type's width.""" mask = (1 << storage_bits) - 1 raw_bits &= mask if signed and raw_bits >= 1 << (storage_bits - 1): raw_bits -= 1 << storage_bits return raw_bits def format_fixed(raw_bits, fractional_bits): """Format a scaled integer exactly as a decimal string.""" if fractional_bits == 0: return str(raw_bits) negative = raw_bits < 0 magnitude = -raw_bits if negative else raw_bits whole = magnitude >> fractional_bits remainder = magnitude & ((1 << fractional_bits) - 1) if remainder == 0: result = str(whole) else: # remainder / 2^n == (remainder * 5^n) / 10^n, which gives a finite, # exact decimal representation without converting through a float. decimal = str(remainder * 5**fractional_bits).zfill(fractional_bits) result = f"{whole}.{decimal.rstrip('0')}" return f"-{result}" if negative else result def _parse_lldb_integer(value): text = value.GetValue() if text is not None: try: return int(text, 0) except ValueError: pass # GetValueAsUnsigned is also useful on LLDB versions that do not render # primitive values through GetValue. It is sufficient for all <= 64-bit # fixed types; current LLDB versions render i128/u128 through GetValue. return value.GetValueAsUnsigned() def fixed_summary(value, _internal_dict): layout = parse_fixed_type(value.GetTypeName()) if layout is None: return None signed, storage_bits, fractional_bits = layout bits = value.GetChildMemberWithName("bits") if not bits.IsValid(): return None raw_bits = _parse_lldb_integer(bits) raw_bits = normalize_bits(raw_bits, signed, storage_bits) return format_fixed(raw_bits, fractional_bits) def __lldb_init_module(debugger, _internal_dict): # LLDB's type-name regex sees the concrete fixed type rather than project # aliases such as FGi32 and FGWide. debugger.HandleCommand( "type summary add --python-function lldb_fixed.fixed_summary " "--category Shinespark --regex " "'^fixed::Fixed(I|U)(8|16|32|64|128)<.+>$'" ) # A dedicated category makes the formatter work even when CodeLLDB has not # enabled rustc's standard `Rust` formatter category yet. debugger.HandleCommand("type category enable Shinespark")