toolbox 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ less prominent and values more readable.
6
6
  """
7
7
 
8
8
  import sys
9
+ import rvalue
9
10
 
10
11
  class Style:
11
12
  """Sentinel object representing a style."""
@@ -28,66 +29,60 @@ method = Style('method') # Alias for symbol (method names)
28
29
  error = Style('error')
29
30
  bold = Style('bold')
30
31
  dim = Style('dim')
32
+ title = Style('title') # For section headers in help text
33
+ placeholder = Style('placeholder') # For help text placeholders (parameters, options)
34
+ example = Style('example') # For example commands in help text
31
35
 
32
36
  class Text:
33
37
  """Plain text output without any formatting."""
34
38
 
35
- def print(self, *args):
36
- """Print arguments, skipping style sentinels."""
37
- result = []
38
- for arg in args:
39
- # Skip Style sentinels
40
- if not isinstance(arg, Style):
41
- # If arg has a print_to method, use it
42
- if hasattr(arg, 'print_to'):
43
- result.append(arg.print_to(self))
44
- else:
45
- result.append(str(arg))
46
- return "".join(result)
47
-
48
- def print_type_tag(self, type_name, address_val=None, details=None):
39
+ def __init__(self, output=None):
40
+ """Initialize text terminal.
41
+
42
+ Args:
43
+ output: Output stream (default: sys.stdout)
44
+ """
45
+ self.output = output or sys.stdout
46
+
47
+ def print(self, *args, end='\n'):
48
+ """Print arguments to output stream.
49
+
50
+ Args:
51
+ *args: Arguments to print (strings, Style sentinels, or objects with print_to)
52
+ end: String appended after the last arg (default: newline)
49
53
  """
50
- Format a type tag like <T_ARRAY@0xABCD embedded length=3>.
54
+ for arg in args:
55
+ if isinstance(arg, Style):
56
+ # Skip style sentinels in plain text mode
57
+ continue
58
+ elif hasattr(arg, 'print_to'):
59
+ # Let object print itself
60
+ arg.print_to(self)
61
+ else:
62
+ self.output.write(str(arg))
63
+ self.output.write(end)
64
+
65
+ def print_type_tag(self, type_name, addr=None, details=None):
66
+ """Print a type tag like <T_ARRAY@0xABCD embedded length=3>.
51
67
 
52
68
  Arguments:
53
69
  type_name: Type name (e.g., "T_ARRAY", "void *")
54
- address_val: Optional hex address (as integer or hex string without 0x)
70
+ addr: Optional hex address (as integer or hex string without 0x)
55
71
  details: Optional details string (e.g., "embedded length=3")
56
-
57
- Returns:
58
- Formatted string with appropriate styling
59
72
  """
60
- if isinstance(address_val, int):
61
- address_val = f"{address_val:x}"
73
+ if isinstance(addr, int):
74
+ addr = f"{addr:x}"
62
75
 
63
- parts = [metadata, '<', type, type_name]
76
+ self.print(metadata, '<', reset, type, type_name, reset, end='')
64
77
 
65
- if address_val:
66
- parts.extend([metadata, f'@0x{address_val}'])
78
+ if addr:
79
+ # @ symbol in dim, address in magenta
80
+ self.print(metadata, '@', reset, address, f'0x{addr}', reset, end='')
67
81
 
68
82
  if details:
69
- parts.extend([f' {details}'])
70
-
71
- parts.extend([metadata, '>', reset])
83
+ self.print(metadata, f' {details}', reset, end='')
72
84
 
73
- return self.print(*parts)
74
-
75
- def print_value_with_tag(self, type_tag, val=None, value_style=value):
76
- """
77
- Format a complete value with type tag and optional value.
78
-
79
- Arguments:
80
- type_tag: Formatted type tag string
81
- val: Optional value to display
82
- value_style: Style sentinel to use for the value
83
-
84
- Returns:
85
- Complete formatted string
86
- """
87
- if val is not None:
88
- return f"{type_tag} {self.print(value_style, str(val), reset)}"
89
- else:
90
- return type_tag
85
+ self.print(metadata, '>', reset, end='')
91
86
 
92
87
  class XTerm(Text):
93
88
  """ANSI color/style output for terminal."""
@@ -112,36 +107,48 @@ class XTerm(Text):
112
107
  BRIGHT_MAGENTA = '\033[95m'
113
108
  BRIGHT_CYAN = '\033[96m'
114
109
 
115
- def __init__(self):
110
+ def __init__(self, output=None):
111
+ """Initialize ANSI terminal.
112
+
113
+ Args:
114
+ output: Output stream (default: sys.stdout)
115
+ """
116
+ super().__init__(output)
116
117
  # Map style sentinels to ANSI codes
117
118
  self.style_map = {
118
119
  reset: self.RESET,
119
- metadata: self.DIM,
120
- address: self.DIM,
121
- type: '',
122
- value: '',
120
+ metadata: self.DIM, # Type tag brackets <>
121
+ address: self.MAGENTA, # Memory addresses in type tags
122
+ type: self.CYAN, # Type names (T_ARRAY, VALUE, etc.)
123
123
  string: self.GREEN,
124
124
  number: self.CYAN,
125
125
  symbol: self.YELLOW,
126
- method: self.YELLOW, # Same as symbol
126
+ method: self.YELLOW, # Same as symbol
127
127
  error: self.RED,
128
128
  bold: self.BOLD,
129
129
  dim: self.DIM,
130
+ title: self.BOLD, # Section headers in help text
131
+ placeholder: self.BLUE, # Help text placeholders
132
+ example: self.GREEN, # Example commands in help text
130
133
  }
131
134
 
132
- def print(self, *args):
133
- """Print arguments, replacing style sentinels with ANSI codes."""
134
- result = []
135
+ def print(self, *args, end='\n'):
136
+ """Print arguments to output stream with ANSI formatting.
137
+
138
+ Args:
139
+ *args: Arguments to print (strings, Style sentinels, or objects with print_to)
140
+ end: String appended after the last arg (default: newline)
141
+ """
135
142
  for arg in args:
136
143
  if isinstance(arg, Style):
137
- result.append(self.style_map.get(arg, ''))
144
+ # Print ANSI code for style
145
+ self.output.write(self.style_map.get(arg, ''))
146
+ elif hasattr(arg, 'print_to'):
147
+ # Let object print itself
148
+ arg.print_to(self)
138
149
  else:
139
- # If arg has a print_to method, use it
140
- if hasattr(arg, 'print_to'):
141
- result.append(arg.print_to(self))
142
- else:
143
- result.append(str(arg))
144
- return "".join(result)
150
+ self.output.write(str(arg))
151
+ self.output.write(end)
145
152
 
146
153
  # Helper for creating the appropriate terminal based on TTY status
147
154
  def create_terminal(from_tty):
@@ -166,7 +173,7 @@ class Printer:
166
173
 
167
174
  def print(self, *args):
168
175
  """Print arguments using the terminal's formatting."""
169
- print(self.terminal.print(*args))
176
+ self.terminal.print(*args)
170
177
 
171
178
  def print_indent(self, depth):
172
179
  """Print indentation based on depth."""
@@ -194,7 +201,5 @@ class Printer:
194
201
  value_int = int(ruby_value)
195
202
  self.debug(f"print_value: value=0x{value_int:x}, depth={depth}")
196
203
 
197
- # Use interpret to get the appropriate wrapper and let it print itself
198
- import value
199
- ruby_object = value.interpret(ruby_value)
204
+ ruby_object = rvalue.interpret(ruby_value)
200
205
  ruby_object.print_recursive(self, depth)
data/data/toolbox/heap.py CHANGED
@@ -1,5 +1,9 @@
1
1
  import debugger
2
2
  import sys
3
+ import command
4
+ import constants
5
+ import format
6
+ import rvalue
3
7
 
4
8
  # Constants
5
9
  RBASIC_FLAGS_TYPE_MASK = 0x1f
@@ -66,15 +70,15 @@ class RubyHeap:
66
70
  return False
67
71
 
68
72
  # Cache commonly used type lookups
69
- self._rbasic_type = debugger.lookup_type('struct RBasic').pointer()
70
- self._value_type = debugger.lookup_type('VALUE')
71
- self._char_ptr_type = debugger.lookup_type('char').pointer()
73
+ self._rbasic_type = constants.type_struct('struct RBasic').pointer()
74
+ self._value_type = constants.type_struct('VALUE')
75
+ self._char_ptr_type = constants.type_struct('char').pointer()
72
76
 
73
77
  # Cache flags field offset for fast memory access
74
78
  # This is critical for LLDB performance where field lookup is expensive
75
79
  try:
76
80
  # Get a dummy RBasic to find the flags offset
77
- rbasic_struct = debugger.lookup_type('struct RBasic')
81
+ rbasic_struct = constants.type_struct('struct RBasic')
78
82
  # In RBasic, 'flags' is the first field (offset 0)
79
83
  # We need to find its offset programmatically for portability
80
84
  fields = rbasic_struct.fields()
@@ -277,9 +281,9 @@ class RubyHeap:
277
281
  return
278
282
 
279
283
  # Cache types for pointer arithmetic and casting
280
- rbasic_type = debugger.lookup_type('struct RBasic')
284
+ rbasic_type = constants.type_struct('struct RBasic')
281
285
  rbasic_ptr_type = rbasic_type.pointer()
282
- char_ptr_type = debugger.lookup_type('char').pointer()
286
+ char_ptr_type = constants.type_struct('char').pointer()
283
287
 
284
288
  for i in range(start_page, allocated_pages):
285
289
  page = self._get_page(i)
@@ -400,7 +404,7 @@ class RubyHeap:
400
404
  T_DATA = 0x0c
401
405
 
402
406
  # Get RTypedData type for casting
403
- rtypeddata_type = debugger.lookup_type('struct RTypedData').pointer()
407
+ rtypeddata_type = constants.type_struct('struct RTypedData').pointer()
404
408
 
405
409
  try:
406
410
  if progress:
@@ -431,9 +435,29 @@ class RubyHeap:
431
435
  # Cast to RTypedData and check type
432
436
  try:
433
437
  typed_data = obj.cast(rtypeddata_type)
438
+ type_field = typed_data['type']
434
439
 
435
- # Compare values directly using __eq__
436
- if typed_data['type'] == data_type:
440
+ # Check if field access failed (returns None when type is incomplete)
441
+ if type_field is None:
442
+ # On first failure, print a helpful error message once
443
+ if not hasattr(self, '_incomplete_type_warning_shown'):
444
+ self._incomplete_type_warning_shown = True
445
+ print("\nError: struct RTypedData debug symbols are incomplete", file=sys.stderr)
446
+ print("Cannot access RTypedData fields with this Ruby version.", file=sys.stderr)
447
+ print("\nThis is a known issue with Ruby 3.4.x on macOS:", file=sys.stderr)
448
+ print(" • A dsymutil bug drops RTypedData from debug symbols", file=sys.stderr)
449
+ print(" • Caused by complex 'const T *const' type in the struct", file=sys.stderr)
450
+ print(" • Fixed in Ruby head (commit ce51ef30df)", file=sys.stderr)
451
+ print("\nWorkarounds:", file=sys.stderr)
452
+ print(" • Use Ruby head: ruby-install ruby-head -- CFLAGS=\"-g -O0\"", file=sys.stderr)
453
+ print(" • Or use GDB on Linux (works with Ruby 3.4.x)", file=sys.stderr)
454
+ print("\nSee: https://socketry.github.io/toolbox/guides/getting-started/", file=sys.stderr)
455
+ print(file=sys.stderr)
456
+ # Can't scan without complete type info
457
+ break
458
+
459
+ # Compare type pointers
460
+ if type_field == data_type:
437
461
  # Return the VALUE, not the extracted data pointer
438
462
  objects.append(obj)
439
463
  if progress:
@@ -450,7 +474,7 @@ class RubyHeap:
450
474
  return objects
451
475
 
452
476
 
453
- class RubyHeapScanCommand(debugger.Command):
477
+ class RubyHeapScanHandler:
454
478
  """Scan the Ruby heap for objects, optionally filtered by type.
455
479
 
456
480
  Usage: rb-heap-scan [--type TYPE] [--limit N] [--from $heap]
@@ -480,23 +504,21 @@ class RubyHeapScanCommand(debugger.Command):
480
504
  rb-heap-scan --from $heap # Continue from last scan
481
505
  """
482
506
 
483
- def __init__(self):
484
- super(RubyHeapScanCommand, self).__init__("rb-heap-scan", debugger.COMMAND_USER)
485
-
486
- def usage(self):
487
- """Print usage information."""
488
- print("Usage: rb-heap-scan [--type TYPE] [--limit N] [--from $heap]")
489
- print("Examples:")
490
- print(" rb-heap-scan --type RUBY_T_STRING # Find up to 10 strings")
491
- print(" rb-heap-scan --type RUBY_T_ARRAY --limit 5 # Find up to 5 arrays")
492
- print(" rb-heap-scan --type 0x05 --limit 100 # Find up to 100 T_STRING objects")
493
- print(" rb-heap-scan --limit 20 # Scan 20 objects (any type)")
494
- print(" rb-heap-scan --type RUBY_T_STRING --from $heap # Continue from last scan")
495
- print()
496
- print("Pagination:")
497
- print(" The address of the last object is saved to $heap for pagination:")
498
- print(" rb-heap-scan --type RUBY_T_STRING --limit 10 # First page")
499
- print(" rb-heap-scan --type RUBY_T_STRING --from $heap # Next page")
507
+ USAGE = command.Usage(
508
+ summary="Scan the Ruby heap for objects, optionally filtered by type",
509
+ parameters=[],
510
+ options={
511
+ 'type': (str, None, 'Filter by Ruby type (e.g., RUBY_T_STRING, RUBY_T_ARRAY, or 0x05)'),
512
+ 'limit': (int, 10, 'Maximum objects to find'),
513
+ 'from': (str, None, 'Start address for pagination (use $heap)')
514
+ },
515
+ flags=[],
516
+ examples=[
517
+ ("rb-heap-scan --type RUBY_T_STRING", "Find up to 10 strings"),
518
+ ("rb-heap-scan --type RUBY_T_ARRAY --limit 20", "Find first 20 arrays"),
519
+ ("rb-heap-scan --from $heap", "Continue from last scan (pagination)")
520
+ ]
521
+ )
500
522
 
501
523
  def _parse_type(self, type_arg):
502
524
  """Parse a type argument and return the type value.
@@ -530,13 +552,9 @@ class RubyHeapScanCommand(debugger.Command):
530
552
 
531
553
  return type_value
532
554
 
533
- def invoke(self, arg, from_tty):
555
+ def invoke(self, arguments, terminal):
534
556
  """Execute the heap scan command."""
535
557
  try:
536
- # Parse arguments
537
- import command
538
- arguments = command.parse_arguments(arg if arg else "")
539
-
540
558
  # Check if we're continuing from a previous scan
541
559
  from_option = arguments.get_option('from')
542
560
  if from_option is not None:
@@ -595,69 +613,61 @@ class RubyHeapScanCommand(debugger.Command):
595
613
  print("(You may have reached the end of the heap)")
596
614
  return
597
615
 
598
- # Import format for terminal output
599
- import format
600
- terminal = format.create_terminal(from_tty)
601
-
602
- # Import value module for interpretation
603
- import value as value_module
604
-
605
616
  print(f"Found {len(objects)} object(s):")
606
617
  print()
607
618
 
608
619
  for i, obj in enumerate(objects):
609
- obj_int = int(obj)
610
-
611
620
  # Set as convenience variable
621
+ obj_int = int(obj)
612
622
  var_name = f"heap{i}"
613
623
  debugger.set_convenience_variable(var_name, obj)
614
624
 
615
625
  # Try to interpret and display the object
616
626
  try:
617
- interpreted = value_module.interpret(obj)
627
+ interpreted = rvalue.interpret(obj)
618
628
 
619
- print(terminal.print(
629
+ terminal.print(
620
630
  format.metadata, f" [{i}] ",
621
631
  format.dim, f"${var_name} = ",
622
632
  format.reset, interpreted
623
- ))
633
+ )
624
634
  except Exception as e:
625
- print(terminal.print(
635
+ terminal.print(
626
636
  format.metadata, f" [{i}] ",
627
637
  format.dim, f"${var_name} = ",
628
638
  format.error, f"<error: {e}>"
629
- ))
639
+ )
630
640
 
631
641
  print()
632
- print(terminal.print(
642
+ terminal.print(
633
643
  format.dim,
634
644
  f"Objects saved in $heap0 through $heap{len(objects)-1}",
635
645
  format.reset
636
- ))
646
+ )
637
647
 
638
648
  # Save next address to $heap for pagination
639
649
  if next_address is not None:
640
650
  # Save the next address to continue from
641
- void_ptr_type = debugger.lookup_type('void').pointer()
651
+ void_ptr_type = constants.type_struct('void').pointer()
642
652
  debugger.set_convenience_variable('heap', debugger.create_value(next_address, void_ptr_type))
643
- print(terminal.print(
653
+ terminal.print(
644
654
  format.dim,
645
655
  f"Next scan address saved to $heap: 0x{next_address:016x}",
646
656
  format.reset
647
- ))
648
- print(terminal.print(
657
+ )
658
+ terminal.print(
649
659
  format.dim,
650
660
  f"Run 'rb-heap-scan --type {type_option if type_option else '...'} --from $heap' for next page",
651
661
  format.reset
652
- ))
662
+ )
653
663
  else:
654
664
  # Reached the end of the heap - unset $heap so next scan starts fresh
655
665
  debugger.set_convenience_variable('heap', None)
656
- print(terminal.print(
666
+ terminal.print(
657
667
  format.dim,
658
668
  f"Reached end of heap (no more objects to scan)",
659
669
  format.reset
660
- ))
670
+ )
661
671
 
662
672
  except Exception as e:
663
673
  print(f"Error: {e}")
@@ -666,4 +676,4 @@ class RubyHeapScanCommand(debugger.Command):
666
676
 
667
677
 
668
678
  # Register commands
669
- RubyHeapScanCommand()
679
+ debugger.register("rb-heap-scan", RubyHeapScanHandler, usage=RubyHeapScanHandler.USAGE)
data/data/toolbox/init.py CHANGED
@@ -24,8 +24,8 @@ failed_extensions = []
24
24
 
25
25
  # Try to load each extension individually
26
26
  extensions_to_load = [
27
- ('object', 'rb-object-print'),
28
- ('context', 'rb-context'),
27
+ ('print', 'rb-print'),
28
+ ('context', 'rb-context, rb-context-storage'),
29
29
  ('fiber', 'rb-fiber-scan-heap, rb-fiber-switch'),
30
30
  ('stack', 'rb-stack-trace'),
31
31
  ('heap', 'rb-heap-scan'),
@@ -33,8 +33,8 @@ extensions_to_load = [
33
33
 
34
34
  for module_name, commands in extensions_to_load:
35
35
  try:
36
- if module_name == 'object':
37
- import object
36
+ if module_name == 'print':
37
+ import print as print_module
38
38
  elif module_name == 'context':
39
39
  import context
40
40
  elif module_name == 'fiber':
@@ -44,8 +44,12 @@ for module_name, commands in extensions_to_load:
44
44
  elif module_name == 'heap':
45
45
  import heap
46
46
  loaded_extensions.append((module_name, commands))
47
- except ImportError as e:
47
+ except Exception as e:
48
+ # Catch all exceptions during module load
48
49
  failed_extensions.append((module_name, str(e)))
50
+ import traceback
51
+ print(f"Failed to load {module_name}: {e}", file=sys.stderr)
52
+ traceback.print_exc(file=sys.stderr)
49
53
 
50
54
  # Silently load - no status messages printed by default
51
55
  # Users can run 'help' to see available commands
@@ -0,0 +1,79 @@
1
+ """Print command for Ruby values."""
2
+
3
+ import debugger
4
+ import sys
5
+
6
+ # Import utilities
7
+ import command
8
+ import constants
9
+ import rvalue
10
+ import rstring
11
+ import rarray
12
+ import rhash
13
+ import rsymbol
14
+ import rstruct
15
+ import rfloat
16
+ import rbignum
17
+ import rbasic
18
+ import format
19
+
20
+
21
+ class RubyObjectPrinter:
22
+ """Print Ruby objects with recursive descent into nested structures."""
23
+
24
+ USAGE = command.Usage(
25
+ summary="Print Ruby objects with recursive inspection",
26
+ parameters=[('value', 'VALUE or expression to print')],
27
+ options={
28
+ 'depth': (int, 1, 'Maximum recursion depth for nested objects')
29
+ },
30
+ flags=[
31
+ ('debug', 'Show internal structure and debug information')
32
+ ],
33
+ examples=[
34
+ ("rb-print $errinfo", "Print exception object"),
35
+ ("rb-print $ec->storage --depth 3", "Print fiber storage with depth 3"),
36
+ ("rb-print $ec->cfp->sp[-1] --debug", "Print top of stack with debug info")
37
+ ]
38
+ )
39
+
40
+ def invoke(self, arguments, terminal):
41
+ """Execute the print command.
42
+
43
+ Args:
44
+ arguments: Parsed Arguments object
45
+ terminal: Terminal formatter (already configured for TTY/non-TTY)
46
+ """
47
+ # Get options
48
+ max_depth = arguments.get_option('depth', 1)
49
+ debug_mode = arguments.has_flag('debug')
50
+
51
+ # Validate depth
52
+ if max_depth < 1:
53
+ print("Error: --depth must be >= 1")
54
+ return
55
+
56
+ # Create printer
57
+ printer = format.Printer(terminal, max_depth, debug_mode)
58
+
59
+ # Process each expression
60
+ for expression in arguments.expressions:
61
+ try:
62
+ # Evaluate the expression
63
+ ruby_value = debugger.parse_and_eval(expression)
64
+
65
+ # Interpret the value and let it print itself recursively
66
+ ruby_object = rvalue.interpret(ruby_value)
67
+ ruby_object.print_recursive(printer, max_depth)
68
+ except debugger.Error as e:
69
+ print(f"Error evaluating expression '{expression}': {e}")
70
+ except Exception as e:
71
+ print(f"Error processing '{expression}': {type(e).__name__}: {e}")
72
+ if debug_mode:
73
+ import traceback
74
+ traceback.print_exc(file=sys.stderr)
75
+
76
+
77
+ # Register command using new interface
78
+ debugger.register("rb-print", RubyObjectPrinter, usage=RubyObjectPrinter.USAGE)
79
+
@@ -77,12 +77,8 @@ class RArrayEmbedded(RArrayBase):
77
77
  def print_to(self, terminal):
78
78
  """Print this array with formatting."""
79
79
  addr = int(self.value)
80
- return terminal.print(
81
- format.metadata, '<',
82
- format.type, 'T_ARRAY',
83
- format.metadata, f'@0x{addr:x} embedded length={len(self)}>',
84
- format.reset
85
- )
80
+ details = f"embedded length={len(self)}"
81
+ terminal.print_type_tag('T_ARRAY', addr, details)
86
82
 
87
83
  class RArrayHeap(RArrayBase):
88
84
  """Heap array (larger arrays with separate memory allocation)."""
@@ -101,12 +97,8 @@ class RArrayHeap(RArrayBase):
101
97
  def print_to(self, terminal):
102
98
  """Print this array with formatting."""
103
99
  addr = int(self.value)
104
- return terminal.print(
105
- format.metadata, '<',
106
- format.type, 'T_ARRAY',
107
- format.metadata, f'@0x{addr:x} heap length={len(self)}>',
108
- format.reset
109
- )
100
+ details = f"heap length={len(self)}"
101
+ terminal.print_type_tag('T_ARRAY', addr, details)
110
102
 
111
103
  def RArray(value):
112
104
  """Factory function that returns the appropriate RArray variant.
@@ -29,39 +29,39 @@ def is_type(value, ruby_type_constant):
29
29
 
30
30
  # Map of type constants to their names for display
31
31
  TYPE_NAMES = {
32
- 'RUBY_T_NONE': 'None',
33
- 'RUBY_T_OBJECT': 'Object',
34
- 'RUBY_T_CLASS': 'Class',
35
- 'RUBY_T_MODULE': 'Module',
36
- 'RUBY_T_FLOAT': 'Float',
37
- 'RUBY_T_STRING': 'String',
38
- 'RUBY_T_REGEXP': 'Regexp',
39
- 'RUBY_T_ARRAY': 'Array',
40
- 'RUBY_T_HASH': 'Hash',
41
- 'RUBY_T_STRUCT': 'Struct',
42
- 'RUBY_T_BIGNUM': 'Bignum',
43
- 'RUBY_T_FILE': 'File',
44
- 'RUBY_T_DATA': 'Data',
45
- 'RUBY_T_MATCH': 'Match',
46
- 'RUBY_T_COMPLEX': 'Complex',
47
- 'RUBY_T_RATIONAL': 'Rational',
48
- 'RUBY_T_NIL': 'Nil',
49
- 'RUBY_T_TRUE': 'True',
50
- 'RUBY_T_FALSE': 'False',
51
- 'RUBY_T_SYMBOL': 'Symbol',
52
- 'RUBY_T_FIXNUM': 'Fixnum',
53
- 'RUBY_T_UNDEF': 'Undef',
54
- 'RUBY_T_IMEMO': 'IMemo',
55
- 'RUBY_T_NODE': 'Node',
56
- 'RUBY_T_ICLASS': 'IClass',
57
- 'RUBY_T_ZOMBIE': 'Zombie',
32
+ 'RUBY_T_NONE': 'T_NONE',
33
+ 'RUBY_T_OBJECT': 'T_OBJECT',
34
+ 'RUBY_T_CLASS': 'T_CLASS',
35
+ 'RUBY_T_MODULE': 'T_MODULE',
36
+ 'RUBY_T_FLOAT': 'T_FLOAT',
37
+ 'RUBY_T_STRING': 'T_STRING',
38
+ 'RUBY_T_REGEXP': 'T_REGEXP',
39
+ 'RUBY_T_ARRAY': 'T_ARRAY',
40
+ 'RUBY_T_HASH': 'T_HASH',
41
+ 'RUBY_T_STRUCT': 'T_STRUCT',
42
+ 'RUBY_T_BIGNUM': 'T_BIGNUM',
43
+ 'RUBY_T_FILE': 'T_FILE',
44
+ 'RUBY_T_DATA': 'T_DATA',
45
+ 'RUBY_T_MATCH': 'T_MATCH',
46
+ 'RUBY_T_COMPLEX': 'T_COMPLEX',
47
+ 'RUBY_T_RATIONAL': 'T_RATIONAL',
48
+ 'RUBY_T_NIL': 'T_NIL',
49
+ 'RUBY_T_TRUE': 'T_TRUE',
50
+ 'RUBY_T_FALSE': 'T_FALSE',
51
+ 'RUBY_T_SYMBOL': 'T_SYMBOL',
52
+ 'RUBY_T_FIXNUM': 'T_FIXNUM',
53
+ 'RUBY_T_UNDEF': 'T_UNDEF',
54
+ 'RUBY_T_IMEMO': 'T_IMEMO',
55
+ 'RUBY_T_NODE': 'T_NODE',
56
+ 'RUBY_T_ICLASS': 'T_ICLASS',
57
+ 'RUBY_T_ZOMBIE': 'T_ZOMBIE',
58
58
  }
59
59
 
60
60
  def type_name(value):
61
61
  """Get the human-readable type name for a VALUE.
62
62
 
63
63
  Returns:
64
- String like 'String', 'Array', 'Hash', etc., or 'Unknown'
64
+ String like 'T_STRING', 'T_ARRAY', 'T_HASH', etc., or 'Unknown(0x...)'
65
65
  """
66
66
  type_flag = type_of(value)
67
67
 
@@ -85,18 +85,14 @@ class RBasic:
85
85
 
86
86
  def __str__(self):
87
87
  type_str = type_name(self.value)
88
- return f"<{type_str}:0x{int(self.value):x}>"
88
+ return f"<{type_str}@0x{int(self.value):x}>"
89
89
 
90
90
  def print_to(self, terminal):
91
- """Return formatted basic object representation."""
91
+ """Print formatted basic object representation."""
92
92
  type_str = type_name(self.value)
93
93
  addr = int(self.value)
94
- return terminal.print(
95
- format.metadata, '<',
96
- format.type, type_str,
97
- format.metadata, f':0x{addr:x}>',
98
- format.reset
99
- )
94
+ # Use print_type_tag for consistency with other types
95
+ terminal.print_type_tag(type_str, addr)
100
96
 
101
97
  def print_recursive(self, printer, depth):
102
98
  """Print this basic object (no recursion)."""