File Coverage

blib/lib/Protocol/PerlDebugCLI.pm
Criterion Covered Total %
statement 60 117 51.2
branch 11 32 34.3
condition 6 12 50.0
subroutine 14 27 51.8
pod 21 21 100.0
total 112 209 53.5


line stmt bran cond sub pod time code
1             package Protocol::PerlDebugCLI;
2             # ABSTRACT: Interact with the Perl debugging interface using events
3 2     2   21150 use strict;
  2         5  
  2         73  
4 2     2   11 use warnings;
  2         5  
  2         68  
5 2     2   1651 use parent qw(Mixin::Event::Dispatch);
  2         622  
  2         12  
6 2     2   14340 use Protocol::PerlDebugCLI::Request;
  2         5  
  2         5069  
7              
8             our $VERSION = '0.002';
9              
10             =head1 NAME
11              
12             Protocol::PerlDebugCLI - generate and process events for interacting with the Perl debug interface
13              
14             =head1 VERSION
15              
16             version 0.002
17              
18             =head1 SYNOPSIS
19              
20             use Protocol::PerlDebugCLI;
21             my $deb = Protocol::PerlDebugCLI->new;
22              
23             # Attach handlers for the events that we're interested in,
24             # anything else will be quietly ignored
25             $deb->add_handler_for_event(
26             breakpoint => sub {
27             warn "Breakpoint reached\n";
28             },
29             );
30              
31             # Set a breakpoint and continue execution
32             $deb->request_breakpoint(
33             file => 'script.pl',
34             line => 17,
35             );
36             $deb->request_continue;
37              
38             =head1 DESCRIPTION
39              
40             This is an abstract implementation for interacting with the perl debugger.
41             It parses the debugger output (provided via the L method) and
42             and generates events, similar in concept to SAX.
43              
44             It also provides abstract methods for generating commands to drive the
45             debugger; by hooking the C event a companion class or subclass
46             can drive the debugger without knowing the details of the protocol.
47              
48             This class is not intended to be used directly - it deals with the abstract
49             protocol and requires additional code to deal with the transport layer
50             (this could be through sockets, via the RemotePort= PERLDBOPTS flag, or on
51             STDIN/STDOUT/TTY for a forked C process).
52              
53             See L for an implementation of a transport
54             layer using RemotePort, and L for a L-based
55             terminal debugging application.
56              
57             Other similar classes for interacting with the debugger are listed in the
58             L section.
59              
60             =head1 METHODS
61              
62             =cut
63              
64             =head2 new
65              
66             Instantiate a new object.
67              
68             =cut
69              
70             sub new {
71 1     1 1 72 my $class = shift;
72 1         3 my $self = bless { }, $class;
73              
74             # Start with no queued requests, and until we get a prompt we're not ready to send requests yet
75 1         11 $self->{queued_requests} = [];
76 1         4 $self->{ready_for_request} = 0;
77              
78             # Auto-send next request on prompt
79             $self->add_handler_for_event(
80             prompt => sub {
81 1     1   31 my ($self, $depth) = @_;
82 1 50       5 $self->send_next_request if $self->have_queued_requests;
83 1         2 $self;
84             }
85 1         12 );
86 1         33 return $self;
87             }
88              
89             =head2 send_next_request
90              
91             Attempt to send the next queued request to the debugger.
92              
93             Expects the caller to have checked whether there are any requests pending,
94             and will raise an exception if this is not the case.
95              
96             =cut
97              
98             sub send_next_request {
99 1     1 1 2 my $self = shift;
100 1 50       2 die "No requests queued" unless my $req = shift @{$self->{queued_requests}};
  1         6  
101              
102 1         5 $self->write($req->command . "\n");
103 1         2 return $self;
104             }
105              
106             =head2 have_queued_requests
107              
108             Returns true if there are queued requests.
109              
110             =cut
111              
112             sub have_queued_requests {
113 1     1 1 2 my $self = shift;
114 1 50       1 return @{$self->{queued_requests}} ? 1 : 0
  1         7  
115             }
116              
117             =head2 current_state
118              
119             Accessor for the current state.
120              
121             Will raise an exception if attempting to set the state to the same value as it had previously.
122              
123             =cut
124              
125             sub current_state {
126 0     0 1 0 my $self = shift;
127 0 0       0 if(@_) {
128 0         0 my $state = shift;
129 0 0       0 die "Attempting to change state to the previous value: $state" if $state eq $self->{state};
130 0         0 $self->{state} = shift;
131 0         0 $self->invoke_event(state_changed => $self->{state});
132 0         0 return $self;
133             }
134 0         0 return $self->{state};
135             }
136              
137             =head2 parse_variable_dump_line
138              
139             Parse variable dump output, typically from v or x commands.
140              
141             =cut
142              
143             sub parse_variable_dump_line {
144 0     0 1 0 my $self = shift;
145 0         0 my $line = shift;
146             # FIXME Only handles simple scalar values at the moment
147 0 0       0 if($line =~ s/^(\$|\%|\*|\@)(\w+)\s*=\s*//) {
148 0   0     0 my $type = {
149             '$' => 'scalar',
150             '@' => 'array',
151             '%' => 'hash',
152             '*' => 'glob',
153             }->{$1} || 'unknown';
154 0         0 $self->invoke_event(have_variable =>
155             type => $type,
156             name => $2,
157             data => $line
158             );
159             }
160 0         0 return $self;
161             }
162              
163             =head2 parse_code_context_line
164              
165             Parse code context, which consists of the current active line and a few surrounding
166             lines.
167              
168             =cut
169              
170             sub parse_code_context_line {
171 0     0 1 0 my $self = shift;
172 0         0 my $line = shift;
173              
174 0 0       0 if($line =~ s/^(\d+)((?:==>)|(?::))?//) {
175 0         0 my $line_number = $1;
176 0   0     0 my $method = {
177             '==>' => 'execution_line',
178             ':' => 'breakable',
179             'none' => 'filler'
180             }->{$2 || 'none'};
181 0         0 $self->invoke_event(code_context =>
182             method => $method,
183             line => $line_number
184             );
185             }
186 0         0 return $self;
187             }
188              
189             =head2 parse_at_breakpoint
190              
191             At a breakpoint we start with the spec, then get line(s) of code
192              
193             =cut
194              
195             sub parse_at_breakpoint {
196 2     2 1 3 my $self = shift;
197 2         4 my $line = shift;
198              
199             # Current file position
200 2 50       14 if($line =~ s/^([\w:]+)\(([^:]+):(\d+)\)://) {
    0          
201 2         9 my ($func, $file, $line) = ($1, $2, $3);
202 2         6 $self->invoke_event(current_position =>
203             function => $func,
204             file => $file,
205             line => $line
206             );
207             } elsif($line =~ s/^(\d+)((?:==>)|(?::))?//) {
208             # Current file position
209 0         0 my $line_number = $1;
210 0   0     0 my $type = {
211             '==>' => 'execution_line',
212             ':' => 'breakable',
213             'none' => 'filler'
214             }->{$2 || 'none'};
215 0         0 $self->invoke_event(surrounding_code =>
216             type => $type,
217             line => $line_number,
218             text => $line
219             );
220             } else {
221 0         0 die "Unknown data: [$line]";
222             }
223 2         1698 return $self;
224             }
225              
226             =head2 on_read
227              
228             Should be called by the transport layer when data is available for parsing.
229              
230             Expects the following parameters:
231              
232             =over 4
233              
234             =item * $buffref - a scalar reference to the current read buffer. Any parseable
235             data will be extract from this buffer, modifying in-place. If there is insufficient
236             data to parse a full line then there may be some data left in this buffer on return,
237             and the transport layer should call us again after reading more data (and not before).
238              
239             =item * $eof - a flag indicating that no further data is forthcoming. When this is set
240             we attempt to parse any trailing data and then go through any required cleanup before
241             returning.
242              
243             =back
244              
245             =cut
246              
247             sub on_read {
248 1     1 1 1041 my ($self, $buffref, $eof) = @_;
249 1         3 $self->{ready_for_request} = 0;
250              
251             # First, parse any full lines we may have already
252 1         10 while($$buffref =~ s/^(.*?)\n//) {
253 7         81 my $line = $1;
254 7 100       26 next unless $line =~ /\w/;
255 5 50 100     46 if($line =~ /^Use /) {
    100 100        
256 0         0 $self->invoke_event(execution_complete =>);
257             # } elsif(@parser) {
258             # $parser[0]->($line);
259             } elsif($line =~ /^Loading DB routines/ || $line =~ /^Editor support/ || $line =~ /^Enter h/) {
260            
261 3         15 $self->invoke_event(unparsed_data => $line);
262             } else {
263 2         6 $self->parse_at_breakpoint($line);
264             }
265             }
266              
267             # Check for prompt
268 1 50       8 if($$buffref =~ s/^ DB<(\d+)> //) {
269 1         2 $self->{ready_for_request} = 1;
270 1         4 $self->invoke_event(prompt => $1);
271             }
272 1         13 return $self;
273             }
274              
275             =head2 is_ready_for_request
276              
277             Returns true if we're ready to send the next request (i.e. we're at a prompt).
278              
279             =cut
280              
281 1     1 1 6 sub is_ready_for_request {shift->{ready_for_request} }
282              
283             =head2 request_stack_trace
284              
285             Request a full stack trace.
286              
287             =cut
288              
289             sub request_stack_trace {
290 0     0 1 0 my $self = shift;
291 0         0 $self->queue_command(
292             command => 'T',
293             );
294              
295 0         0 return $self;
296             }
297              
298             =head2 request_vars_in_scope
299              
300             Request a dump of all vars in the current scope.
301              
302             =cut
303              
304             sub request_vars_in_scope {
305 0     0 1 0 my $self = shift;
306             $self->queue_command(
307             command => 'y',
308             on_start => sub {
309 0     0   0 $self->{scope_vars} = [];
310             },
311 0         0 parser => 'parse_var_info',
312             );
313 0         0 return $self;
314             }
315              
316             =head2 request_current_line
317              
318             Request information about the current line (i.e. next line to be executed).
319              
320             =cut
321              
322             sub request_current_line {
323 1     1 1 5 my $self = shift;
324 1         4 $self->queue_command(
325             command => 'y',
326             );
327 1         4 return $self;
328             }
329              
330             =head2 request_step_into
331              
332             Step into the current line.
333              
334             =cut
335              
336             sub request_step_into {
337 0     0 1 0 my $self = shift;
338 0         0 $self->queue_command(
339             command => 's',
340             );
341 0         0 return $self;
342             }
343              
344             =head2 request_step_over
345              
346             Step over the current line.
347              
348             =cut
349              
350             sub request_step_over {
351 0     0 1 0 my $self = shift;
352 0         0 $self->queue_command(
353             command => 'n',
354             );
355 0         0 return $self;
356             }
357              
358             =head2 request_continue
359              
360             Continue execution.
361              
362             =cut
363              
364             sub request_continue {
365 0     0 1 0 my $self = shift;
366 0         0 $self->queue_command(
367             command => 'c',
368             );
369 0         0 return $self;
370             }
371              
372             =head2 request_breakpoint
373              
374             Set a breakpoint on the requested line.
375              
376             =cut
377              
378             sub request_breakpoint {
379 0     0 1 0 my $self = shift;
380 0         0 my %args = @_;
381 0 0       0 $self->queue_command(
382             command => 'b' . (exists $args{line} ? ' ' . $args{line} : ''),
383             );
384 0         0 return $self;
385             }
386              
387             =head2 request_clear_breakpoint
388              
389             Clear the given breakpoint.
390              
391             Expects the following named parameters:
392              
393             =over 4
394              
395             =item * line - (optional) line number to clear breakpoints from
396              
397             =back
398              
399             If no line is provided, will clear all existing breakpoints.
400              
401             =cut
402              
403             sub request_clear_breakpoint {
404 0     0 1 0 my $self = shift;
405 0         0 my %args = @_;
406 0 0       0 $self->queue_command(
407             command => 'B ' . (exists $args{line} ? $args{line} : '*'),
408             );
409 0         0 return $self;
410             }
411              
412             =head2 request_restart
413              
414             Restart the current program.
415              
416             =cut
417              
418             sub request_restart {
419 0     0 1 0 my $self = shift;
420 0         0 $self->queue_command(
421             command => 'R',
422             );
423 0         0 return $self;
424             }
425              
426             =head2 request_watch
427              
428             Request a watch on the given variable.
429              
430             =cut
431              
432             sub request_watch {
433 0     0 1 0 my $self = shift;
434 0         0 my %args = @_;
435 0         0 $self->queue_command(
436             command => 'w ' . $args{variable},
437             );
438 0         0 return $self;
439             }
440              
441             =head2 queue_command
442              
443             Queue the given command.
444              
445             =cut
446              
447             sub queue_command {
448 1     1 1 2 my $self = shift;
449 1         9 my $req = Protocol::PerlDebugCLI::Request->new(@_);
450 1         1 push @{$self->{queued_requests}}, $req;
  1         20  
451 1 50       4 $self->send_next_request if $self->is_ready_for_request;
452 1         8 return $self;
453             }
454              
455             =head2 write
456              
457             Invokes a C event, requesting the given data be written to the
458             underlying transport.
459              
460             =cut
461              
462             sub write {
463 1     1 1 2 my $self = shift;
464 1         4 $self->invoke_event(write => shift);
465 1         357 return $self;
466             }
467              
468             1;
469              
470             __END__