toolbox 0.1.3 → 0.2.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.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/bake/ruby/gdb.rb +135 -0
  4. data/bake/toolbox/gdb.rb +137 -0
  5. data/bake/toolbox/lldb.rb +137 -0
  6. data/context/fiber-debugging.md +171 -0
  7. data/context/getting-started.md +178 -0
  8. data/context/heap-debugging.md +351 -0
  9. data/context/index.yaml +28 -0
  10. data/context/object-inspection.md +208 -0
  11. data/context/stack-inspection.md +188 -0
  12. data/data/toolbox/command.py +254 -0
  13. data/data/toolbox/constants.py +200 -0
  14. data/data/toolbox/context.py +295 -0
  15. data/data/toolbox/debugger/__init__.py +99 -0
  16. data/data/toolbox/debugger/gdb_backend.py +595 -0
  17. data/data/toolbox/debugger/lldb_backend.py +885 -0
  18. data/data/toolbox/fiber.py +885 -0
  19. data/data/toolbox/format.py +200 -0
  20. data/data/toolbox/heap.py +669 -0
  21. data/data/toolbox/init.py +85 -0
  22. data/data/toolbox/object.py +84 -0
  23. data/data/toolbox/rarray.py +124 -0
  24. data/data/toolbox/rbasic.py +103 -0
  25. data/data/toolbox/rbignum.py +52 -0
  26. data/data/toolbox/rclass.py +136 -0
  27. data/data/toolbox/readme.md +214 -0
  28. data/data/toolbox/rexception.py +150 -0
  29. data/data/toolbox/rfloat.py +98 -0
  30. data/data/toolbox/rhash.py +159 -0
  31. data/data/toolbox/rstring.py +234 -0
  32. data/data/toolbox/rstruct.py +157 -0
  33. data/data/toolbox/rsymbol.py +302 -0
  34. data/data/toolbox/stack.py +630 -0
  35. data/data/toolbox/value.py +183 -0
  36. data/lib/toolbox/gdb.rb +21 -0
  37. data/lib/toolbox/lldb.rb +21 -0
  38. data/lib/toolbox/version.rb +7 -1
  39. data/lib/toolbox.rb +9 -24
  40. data/license.md +21 -0
  41. data/readme.md +64 -0
  42. data/releases.md +9 -0
  43. data.tar.gz.sig +2 -0
  44. metadata +95 -165
  45. metadata.gz.sig +0 -0
  46. data/Rakefile +0 -57
  47. data/lib/dirs.rb +0 -9
  48. data/lib/toolbox/config.rb +0 -211
  49. data/lib/toolbox/default_controller.rb +0 -393
  50. data/lib/toolbox/helpers.rb +0 -11
  51. data/lib/toolbox/rendering.rb +0 -413
  52. data/lib/toolbox/searching.rb +0 -85
  53. data/lib/toolbox/session_params.rb +0 -63
  54. data/lib/toolbox/sorting.rb +0 -74
  55. data/locale/de/LC_MESSAGES/toolbox.mo +0 -0
  56. data/public/images/add.png +0 -0
  57. data/public/images/arrow_down.gif +0 -0
  58. data/public/images/arrow_up.gif +0 -0
  59. data/public/images/close.png +0 -0
  60. data/public/images/edit.gif +0 -0
  61. data/public/images/email.png +0 -0
  62. data/public/images/page.png +0 -0
  63. data/public/images/page_acrobat.png +0 -0
  64. data/public/images/page_add.png +0 -0
  65. data/public/images/page_copy.png +0 -0
  66. data/public/images/page_delete.png +0 -0
  67. data/public/images/page_edit.png +0 -0
  68. data/public/images/page_excel.png +0 -0
  69. data/public/images/page_list.png +0 -0
  70. data/public/images/page_save.png +0 -0
  71. data/public/images/page_word.png +0 -0
  72. data/public/images/remove.png +0 -0
  73. data/public/images/show.gif +0 -0
  74. data/public/images/spinner.gif +0 -0
  75. data/public/javascripts/popup.js +0 -498
  76. data/public/javascripts/toolbox.js +0 -18
  77. data/public/stylesheets/context_menu.css +0 -168
  78. data/public/stylesheets/popup.css +0 -30
  79. data/public/stylesheets/toolbox.css +0 -107
  80. data/view/toolbox/_collection.html.erb +0 -24
  81. data/view/toolbox/_collection_header.html.erb +0 -7
  82. data/view/toolbox/_context_menu.html.erb +0 -17
  83. data/view/toolbox/_dialogs.html.erb +0 -6
  84. data/view/toolbox/_form.html.erb +0 -30
  85. data/view/toolbox/_form_collection_row.html.erb +0 -18
  86. data/view/toolbox/_form_fieldset.html.erb +0 -30
  87. data/view/toolbox/_form_fieldset_row.html.erb +0 -19
  88. data/view/toolbox/_list.html.erb +0 -25
  89. data/view/toolbox/_list_row.html.erb +0 -10
  90. data/view/toolbox/_menu.html.erb +0 -7
  91. data/view/toolbox/_search_field.html.erb +0 -8
  92. data/view/toolbox/_show.html.erb +0 -12
  93. data/view/toolbox/_show_collection_row.html.erb +0 -6
  94. data/view/toolbox/_show_fieldset.html.erb +0 -21
  95. data/view/toolbox/edit.html.erb +0 -5
  96. data/view/toolbox/index.html.erb +0 -3
  97. data/view/toolbox/new.html.erb +0 -9
  98. data/view/toolbox/show.html.erb +0 -39
@@ -0,0 +1,208 @@
1
+ # Object Inspection
2
+
3
+ This guide explains how to use `rb-object-print` to inspect Ruby objects, hashes, arrays, and structs in GDB.
4
+
5
+ ## Why Object Inspection Matters
6
+
7
+ When debugging Ruby programs or analyzing core dumps, you often need to inspect complex data structures that are difficult to read in their raw memory representation. Standard GDB commands show pointer addresses and raw memory, but not the logical structure of Ruby objects.
8
+
9
+ Use `rb-object-print` when you need:
10
+
11
+ - **Understand exception objects**: See the full exception hierarchy, message, and backtrace data
12
+ - **Inspect fiber storage**: View thread-local data and fiber-specific variables
13
+ - **Debug data corruption**: Check the contents of hashes and arrays for unexpected values
14
+ - **Analyze VM state**: Examine objects on the VM stack without manual pointer arithmetic
15
+
16
+ ## Basic Usage
17
+
18
+ The `rb-object-print` command recursively prints Ruby objects in a human-readable format.
19
+
20
+ ### Syntax
21
+
22
+ ~~~
23
+ rb-object-print <expression> [--depth N] [--debug]
24
+ ~~~
25
+
26
+ Where:
27
+ - `<expression>`: Any GDB expression that evaluates to a Ruby VALUE
28
+ - `--depth N`: Maximum recursion depth (default: 1)
29
+ - `--debug`: Enable diagnostic output for troubleshooting
30
+
31
+ ### Simple Values
32
+
33
+ Print immediate values and special constants:
34
+
35
+ ~~~
36
+ (gdb) rb-object-print 0 # <T_FALSE>
37
+ (gdb) rb-object-print 8 # <T_NIL>
38
+ (gdb) rb-object-print 20 # <T_TRUE>
39
+ (gdb) rb-object-print 85 # <T_FIXNUM> 42
40
+ ~~~
41
+
42
+ These work without any Ruby process running, making them useful for learning and testing.
43
+
44
+ ### Expressions
45
+
46
+ Use any valid GDB expression:
47
+
48
+ ~~~
49
+ (gdb) rb-object-print $ec->errinfo # Exception object
50
+ (gdb) rb-object-print $ec->cfp->sp[-1] # Top of VM stack
51
+ (gdb) rb-object-print $ec->storage # Fiber storage hash
52
+ (gdb) rb-object-print (VALUE)0x00007f8a12345678 # Object at specific address
53
+ ~~~
54
+
55
+ ## Inspecting Hashes
56
+
57
+ Ruby hashes have two internal representations (ST table and AR table). The command automatically detects and displays both:
58
+
59
+ ### Small Hashes (AR Table)
60
+
61
+ For hashes with fewer than 8 entries, Ruby uses an array-based implementation:
62
+
63
+ ~~~
64
+ (gdb) rb-object-print $some_hash
65
+ <T_HASH@...>
66
+ [ 0] K: <T_SYMBOL> :name
67
+ V: <T_STRING@...> "Alice"
68
+ [ 1] K: <T_SYMBOL> :age
69
+ V: <T_FIXNUM> 30
70
+ [ 2] K: <T_SYMBOL> :active
71
+ V: <T_TRUE>
72
+ ~~~
73
+
74
+ ### Large Hashes (ST Table)
75
+
76
+ For larger hashes, Ruby uses a hash table (output format is similar):
77
+
78
+ ~~~
79
+ (gdb) rb-object-print $large_hash
80
+ <T_HASH@...>
81
+ [ 0] K: <T_SYMBOL> :user_id
82
+ V: <T_FIXNUM> 12345
83
+ [ 1] K: <T_SYMBOL> :session_data
84
+ V: <T_STRING@...> "..."
85
+ ...
86
+ ~~~
87
+
88
+ ### Controlling Depth
89
+
90
+ Prevent overwhelming output from deeply nested structures:
91
+
92
+ ~~~
93
+ (gdb) rb-object-print $nested_hash --depth 1 # Only top level
94
+ <T_HASH@...>
95
+ [ 0] K: <T_SYMBOL> :data
96
+ V: <T_HASH@...> # Nested hash not expanded
97
+
98
+ (gdb) rb-object-print $nested_hash --depth 2 # Expand one level
99
+ <T_HASH@...>
100
+ [ 0] K: <T_SYMBOL> :data
101
+ V: <T_HASH@...>
102
+ [ 0] K: <T_SYMBOL> :nested_key
103
+ V: <T_STRING@...> "value"
104
+ ~~~
105
+
106
+ At depth 1, nested structures show their type and address but aren't expanded. Increase depth to expand them.
107
+
108
+ ## Inspecting Arrays
109
+
110
+ Arrays also have two representations based on size:
111
+
112
+ ### Arrays
113
+
114
+ Arrays display their elements with type information:
115
+
116
+ ~~~
117
+ (gdb) rb-object-print $array
118
+ <T_ARRAY@...>
119
+ [ 0] <T_FIXNUM> 1
120
+ [ 1] <T_FIXNUM> 2
121
+ [ 2] <T_FIXNUM> 3
122
+ ~~~
123
+
124
+ For arrays with nested objects:
125
+
126
+ ~~~
127
+ (gdb) rb-object-print $array --depth 2
128
+ <T_ARRAY@...>
129
+ [ 0] <T_STRING@...> "first item"
130
+ [ 1] <T_HASH@...>
131
+ [ 0] K: <T_SYMBOL> :key
132
+ V: <T_FIXNUM> 123
133
+ ...
134
+ ~~~
135
+
136
+ ## Inspecting Structs
137
+
138
+ Ruby Struct objects work similarly to arrays:
139
+
140
+ ~~~
141
+ (gdb) rb-object-print $struct_instance
142
+ <T_STRUCT@...>
143
+ [ 0] <T_STRING@...> "John"
144
+ [ 1] <T_FIXNUM> 25
145
+ [ 2] <T_STRING@...> "Engineer"
146
+ [ 3] <T_TRUE>
147
+ ~~~
148
+
149
+ ## Practical Examples
150
+
151
+ ### Debugging Exception in Fiber
152
+
153
+ When a fiber has an exception, inspect it:
154
+
155
+ ~~~
156
+ (gdb) rb-fiber-scan-heap
157
+ (gdb) rb-fiber-scan-switch 5 # Switch to fiber #5
158
+ (gdb) rb-object-print $errinfo --depth 3
159
+ ~~~
160
+
161
+ This reveals the full exception structure including any nested causes. After switching to a fiber, `$errinfo` and `$ec` convenience variables are automatically set.
162
+
163
+ ### Inspecting Method Arguments
164
+
165
+ Break at a method and examine arguments on the stack:
166
+
167
+ ~~~
168
+ (gdb) break some_method
169
+ (gdb) run
170
+ (gdb) rb-object-print $ec->cfp->sp[-1] # Last argument
171
+ (gdb) rb-object-print $ec->cfp->sp[-2] # Second-to-last argument
172
+ ~~~
173
+
174
+ ### Examining Fiber Storage
175
+
176
+ Thread-local variables are stored in fiber storage:
177
+
178
+ ~~~
179
+ (gdb) rb-fiber-scan-heap
180
+ (gdb) rb-fiber-scan-switch 0
181
+ (gdb) rb-object-print $ec->storage --depth 2
182
+ ~~~
183
+
184
+ This shows all thread-local variables and their values.
185
+
186
+ ## Debugging with --debug Flag
187
+
188
+ When `rb-object-print` doesn't show what you expect, use `--debug`:
189
+
190
+ ~~~
191
+ (gdb) rb-object-print $suspicious_value --debug
192
+ DEBUG: Evaluated '$suspicious_value' to 0x7f8a1c567890
193
+ DEBUG: Loaded constant RUBY_T_MASK = 31
194
+ DEBUG: Object at 0x7f8a1c567890 with flags=0x20040005, type=0x5
195
+ ...
196
+ ~~~
197
+
198
+ This shows:
199
+ - How the expression was evaluated
200
+ - What constants were loaded
201
+ - Object type detection logic
202
+ - Any errors encountered
203
+
204
+ Use this to troubleshoot:
205
+ - Unexpected output format
206
+ - Missing nested structures
207
+ - Type detection issues
208
+ - Access errors
@@ -0,0 +1,188 @@
1
+ # Stack Inspection
2
+
3
+ This guide explains how to inspect both Ruby VM stacks and native C stacks when debugging Ruby programs.
4
+
5
+ ## Understanding Ruby's Dual Stack System
6
+
7
+ Ruby programs operate with two distinct stacks that serve different purposes. Understanding both is crucial for effective debugging, especially when tracking down segfaults, stack overflows, or unexpected behavior in C extensions.
8
+
9
+ Use stack inspection when you need:
10
+
11
+ - **Trace execution flow**: Understand the sequence of method calls that led to the current state
12
+ - **Debug C extensions**: See both Ruby and native frames when extensions are involved
13
+ - **Find stack overflows**: Identify deep recursion in either Ruby or C code
14
+ - **Understand fiber switches**: See where fibers yield and resume
15
+
16
+ ## The Two Stack Types
17
+
18
+ ### VM Stack (Ruby Level)
19
+
20
+ The VM stack holds:
21
+ - Ruby method call frames (control frames)
22
+ - Local variables and temporaries
23
+ - Method arguments
24
+ - Block parameters
25
+ - Return values
26
+
27
+ This is what you see with Ruby's `caller` method at runtime.
28
+
29
+ ### C Stack (Native Level)
30
+
31
+ The C/machine stack holds:
32
+ - Native function call frames
33
+ - C local variables
34
+ - Saved registers
35
+ - Return addresses
36
+
37
+ This is what GDB's `bt` command shows by default.
38
+
39
+ ## Inspecting VM Stacks
40
+
41
+ ### Current Frame Information
42
+
43
+ See the current Ruby control frame:
44
+
45
+ ~~~
46
+ (gdb) set $ec = ruby_current_execution_context_ptr
47
+ (gdb) set $cfp = $ec->cfp
48
+ (gdb) p $cfp->pc # Program counter
49
+ (gdb) p $cfp->sp # Stack pointer
50
+ (gdb) p $cfp->iseq # Instruction sequence
51
+ (gdb) p $cfp->ep # Environment pointer
52
+ ~~~
53
+
54
+ ### Combined Stack Trace
55
+
56
+ The simplest way to see both Ruby and C frames:
57
+
58
+ ~~~
59
+ (gdb) rb-fiber-scan-heap
60
+ (gdb) rb-fiber-scan-switch 5
61
+ (gdb) rb-stack-trace
62
+ Combined Ruby/C backtrace for Fiber #5:
63
+ [C] fiber_setcontext
64
+ [R] /app/lib/connection.rb:123:in `read'
65
+ [C] rb_io_wait_readable
66
+ [R] /app/lib/connection.rb:89:in `receive'
67
+ ...
68
+ ~~~
69
+
70
+ This shows both levels of the call stack in order.
71
+
72
+ ### Detailed Frame Information
73
+
74
+ For advanced debugging, you can inspect the raw VM frames:
75
+
76
+ ~~~
77
+ (gdb) set $ec = ruby_current_execution_context_ptr
78
+ (gdb) set $cfp = $ec->cfp
79
+
80
+ # Current frame details:
81
+ (gdb) p $cfp->pc # Program counter
82
+ (gdb) p $cfp->sp # Stack pointer
83
+ (gdb) p $cfp->iseq # Instruction sequence
84
+ (gdb) p $cfp->ep # Environment pointer
85
+ ~~~
86
+
87
+ ## Inspecting C Stacks
88
+
89
+ ### C Backtrace with GDB
90
+
91
+ After switching to a fiber, use standard GDB commands to inspect the C stack:
92
+
93
+ ~~~
94
+ (gdb) rb-fiber-scan-switch 5
95
+ (gdb) bt # Show C backtrace
96
+ (gdb) frame 2 # Switch to specific frame
97
+ (gdb) info args # Show function arguments
98
+ (gdb) info locals # Show local variables
99
+ ~~~
100
+
101
+ The fiber unwinder automatically integrates with GDB's backtrace functionality, so `bt` shows the correct C stack for the selected fiber.
102
+
103
+ ## Practical Examples
104
+
105
+ ### Finding Where Execution Stopped
106
+
107
+ Identify the exact location in both Ruby and C:
108
+
109
+ ~~~
110
+ (gdb) rb-fiber-scan-heap
111
+ (gdb) rb-fiber-scan-switch 5 # Switch to fiber context
112
+ (gdb) rb-stack-trace # Combined backtrace
113
+ [R] /app/lib/connection.rb:123:in `read'
114
+ [C] rb_fiber_yield
115
+ [C] rb_io_wait_readable
116
+ [R] /app/lib/connection.rb:89:in `receive'
117
+ ~~~
118
+
119
+ This shows the fiber is suspended in `read`, waiting for I/O.
120
+
121
+ ### Debugging Deep Recursion
122
+
123
+ Detect excessive call depth:
124
+
125
+ ~~~
126
+ (gdb) rb-fiber-scan-heap
127
+ (gdb) rb-fiber-scan-switch 5
128
+ (gdb) rb-stack-trace | grep "/app/lib/parser.rb:45" | wc -l
129
+ 134 # Same line appearing 134 times!
130
+ ~~~
131
+
132
+ Identifies a recursion issue in the parser.
133
+
134
+ ### Examining Stack Values
135
+
136
+ See what values are on the current frame's stack:
137
+
138
+ ~~~
139
+ (gdb) rb-fiber-scan-switch 5
140
+ (gdb) set $sp = $ec->cfp->sp
141
+
142
+ # Print values on stack
143
+ (gdb) rb-object-print *(VALUE*)($sp - 1) # Top of stack
144
+ (gdb) rb-object-print *(VALUE*)($sp - 2) # Second value
145
+ (gdb) rb-object-print *(VALUE*)($sp - 3) # Third value
146
+ ~~~
147
+
148
+ ### Tracking Fiber Switches
149
+
150
+ See the call stack across fiber boundaries:
151
+
152
+ ~~~
153
+ (gdb) rb-fiber-scan-switch 5
154
+ (gdb) bt
155
+ #0 fiber_setcontext
156
+ #1 rb_fiber_yield # Fiber yielded here
157
+ #2 rb_io_wait_readable # Waiting for I/O
158
+ #3 some_io_operation
159
+
160
+ (gdb) frame 3
161
+ (gdb) info locals # C variables at I/O call
162
+ ~~~
163
+
164
+ ## Combining VM and C Stacks
165
+
166
+ For the complete picture, use the combined stack trace:
167
+
168
+ ~~~
169
+ (gdb) rb-fiber-scan-switch 5
170
+ (gdb) rb-stack-trace
171
+ Combined Ruby/C backtrace:
172
+ [R] /app/lib/connection.rb:123:in `read'
173
+ [C] rb_io_wait_readable
174
+ [C] rb_io_read
175
+ [R] /app/lib/connection.rb:89:in `receive'
176
+ [C] rb_fiber_yield
177
+ [R] /app/lib/server.rb:56:in `handle_client'
178
+ ...
179
+ ~~~
180
+
181
+ Or separately:
182
+
183
+ ~~~
184
+ (gdb) rb-stack-trace --values # Ruby perspective with stack values
185
+ (gdb) bt # C perspective only
186
+ ~~~
187
+
188
+ The combined view reveals the full execution path through both Ruby and C code.
@@ -0,0 +1,254 @@
1
+ class Arguments:
2
+ """Structured result from parsing GDB command arguments.
3
+
4
+ Attributes:
5
+ expressions: List of expressions to evaluate (e.g., ["$var", "$ec->cfp->sp[-1]"])
6
+ flags: Set of boolean flags (e.g., {'debug'})
7
+ options: Dict of options with values (e.g., {'depth': 3})
8
+ """
9
+ def __init__(self, expressions, flags, options):
10
+ self.expressions = expressions
11
+ self.flags = flags
12
+ self.options = options
13
+
14
+ def has_flag(self, flag_name):
15
+ """Check if a boolean flag is present"""
16
+ return flag_name in self.flags
17
+
18
+ def get_option(self, option_name, default=None):
19
+ """Get an option value with optional default"""
20
+ return self.options.get(option_name, default)
21
+
22
+ class ArgumentParser:
23
+ """Parse GDB command arguments handling nested brackets, quotes, and flags.
24
+
25
+ This parser correctly handles:
26
+ - Nested brackets: $ec->cfp->sp[-1]
27
+ - Nested parentheses: ((struct foo*)bar)->baz
28
+ - Quotes: "string value"
29
+ - Flags: --debug, --depth 3
30
+ - Complex expressions: foo + 10, bar->ptr[idx * 2]
31
+ """
32
+
33
+ def __init__(self, argument_string):
34
+ self.argument_string = argument_string.strip()
35
+ self.position = 0
36
+ self.length = len(self.argument_string)
37
+
38
+ def parse(self):
39
+ """Parse the argument string and return (expressions, flags, options)
40
+
41
+ Returns:
42
+ tuple: (expressions: list[str], flags: set, options: dict)
43
+ - expressions: List of expressions to evaluate
44
+ - flags: Set of boolean flags (e.g., {'debug'})
45
+ - options: Dict of options with values (e.g., {'depth': 3})
46
+ """
47
+ flags = set()
48
+ options = {}
49
+ expressions = []
50
+
51
+ while self.position < self.length:
52
+ self.skip_whitespace()
53
+ if self.position >= self.length:
54
+ break
55
+
56
+ if self.peek() == '-' and self.peek(1) == '-':
57
+ # Parse a flag or option
58
+ flag_name, flag_value = self.parse_flag()
59
+ if flag_value is None:
60
+ flags.add(flag_name)
61
+ else:
62
+ options[flag_name] = flag_value
63
+ else:
64
+ # Parse an expression
65
+ expression = self.parse_expression()
66
+ if expression:
67
+ expressions.append(expression)
68
+
69
+ return expressions, flags, options
70
+
71
+ def peek(self, offset=0):
72
+ """Peek at character at current position + offset"""
73
+ position = self.position + offset
74
+ if position < self.length:
75
+ return self.argument_string[position]
76
+ return None
77
+
78
+ def consume(self, count=1):
79
+ """Consume and return count characters"""
80
+ result = self.argument_string[self.position:self.position + count]
81
+ self.position += count
82
+ return result
83
+
84
+ def skip_whitespace(self):
85
+ """Skip whitespace characters"""
86
+ while self.position < self.length and self.argument_string[self.position].isspace():
87
+ self.position += 1
88
+
89
+ def parse_flag(self):
90
+ """Parse a flag starting with --
91
+
92
+ Returns:
93
+ tuple: (flag_name: str, value: str|None)
94
+ - For boolean flags: ('debug', None)
95
+ - For valued flags: ('depth', '3')
96
+ """
97
+ # Consume '--'
98
+ self.consume(2)
99
+
100
+ # Read flag name
101
+ flag_name = ''
102
+ while self.position < self.length and self.argument_string[self.position].isalnum():
103
+ flag_name += self.consume()
104
+
105
+ # Check if flag has a value
106
+ self.skip_whitespace()
107
+
108
+ # If next character is not another flag and not end of string, it might be a value
109
+ if self.position < self.length and not (self.peek() == '-' and self.peek(1) == '-'):
110
+ # Try to parse a value - it could start with $, digit, or letter
111
+ if self.peek() and not self.peek().isspace():
112
+ value = ''
113
+ while self.position < self.length and not self.argument_string[self.position].isspace():
114
+ if self.peek() == '-' and self.peek(1) == '-':
115
+ break
116
+ value += self.consume()
117
+
118
+ # Try to convert to int if it's a number
119
+ try:
120
+ return flag_name, int(value)
121
+ except ValueError:
122
+ return flag_name, value
123
+
124
+ return flag_name, None
125
+
126
+ def parse_expression(self):
127
+ """Parse a single expression, stopping at whitespace (unless nested) or flags.
128
+
129
+ An expression can be:
130
+ - A quoted string: "foo" or 'bar'
131
+ - A parenthesized expression: (x + y)
132
+ - A variable with accessors: $ec->cfp->sp[-1]
133
+ - Any combination that doesn't contain unquoted/unnested whitespace
134
+ """
135
+ expression = ''
136
+
137
+ while self.position < self.length:
138
+ self.skip_whitespace()
139
+ if self.position >= self.length:
140
+ break
141
+
142
+ character = self.peek()
143
+
144
+ # Stop at flags
145
+ if character == '-' and self.peek(1) == '-':
146
+ break
147
+
148
+ # Handle quoted strings - these are complete expressions
149
+ if character in ('"', "'"):
150
+ quoted = self.parse_quoted_string(character)
151
+ expression += quoted
152
+ # After a quoted string, we're done with this expression
153
+ break
154
+
155
+ # Handle parentheses - collect the whole balanced expression
156
+ if character == '(':
157
+ expression += self.parse_balanced('(', ')')
158
+ continue
159
+
160
+ # Handle brackets
161
+ if character == '[':
162
+ expression += self.parse_balanced('[', ']')
163
+ continue
164
+
165
+ # Handle braces
166
+ if character == '{':
167
+ expression += self.parse_balanced('{', '}')
168
+ continue
169
+
170
+ # Stop at whitespace (this separates expressions)
171
+ if character.isspace():
172
+ break
173
+
174
+ # Regular character - part of a variable name, operator, etc.
175
+ expression += self.consume()
176
+
177
+ return expression.strip()
178
+
179
+ def parse_quoted_string(self, quote_character):
180
+ """Parse a quoted string, handling escapes"""
181
+ result = self.consume() # Opening quote
182
+
183
+ while self.position < self.length:
184
+ character = self.peek()
185
+
186
+ if character == '\\':
187
+ # Escape sequence
188
+ result += self.consume()
189
+ if self.position < self.length:
190
+ result += self.consume()
191
+ elif character == quote_character:
192
+ # Closing quote
193
+ result += self.consume()
194
+ break
195
+ else:
196
+ result += self.consume()
197
+
198
+ return result
199
+
200
+ def parse_balanced(self, open_character, close_character):
201
+ """Parse a balanced pair of delimiters (e.g., parentheses, brackets)"""
202
+ result = self.consume() # Opening delimiter
203
+ depth = 1
204
+
205
+ while self.position < self.length and depth > 0:
206
+ character = self.peek()
207
+
208
+ # Handle quotes inside balanced delimiters
209
+ if character in ('"', "'"):
210
+ result += self.parse_quoted_string(character)
211
+ continue
212
+
213
+ if character == open_character:
214
+ depth += 1
215
+ elif character == close_character:
216
+ depth -= 1
217
+
218
+ result += self.consume()
219
+
220
+ return result
221
+
222
+ def parse_arguments(input):
223
+ """Convenience function to parse argument string.
224
+
225
+ Arguments:
226
+ input: The raw argument string from GDB command
227
+
228
+ Returns:
229
+ Arguments: Structured object with expressions, flags, and options
230
+
231
+ Examples:
232
+ >>> arguments = parse_arguments("$var --debug")
233
+ >>> arguments.expressions
234
+ ['$var']
235
+ >>> arguments.has_flag('debug')
236
+ True
237
+
238
+ >>> arguments = parse_arguments('"foo" "bar" --depth 3')
239
+ >>> arguments.expressions
240
+ ['"foo"', '"bar"']
241
+ >>> arguments.get_option('depth')
242
+ 3
243
+
244
+ >>> arguments = parse_arguments("$ec->cfp->sp[-1] --debug --depth 2")
245
+ >>> arguments.expressions
246
+ ['$ec->cfp->sp[-1]']
247
+ >>> arguments.has_flag('debug')
248
+ True
249
+ >>> arguments.get_option('depth')
250
+ 2
251
+ """
252
+ parser = ArgumentParser(input)
253
+ expressions, flags, options = parser.parse()
254
+ return Arguments(expressions, flags, options)