File Coverage

blib/lib/Syntax/Feature/Try.pm
Criterion Covered Total %
statement 68 68 100.0
branch 25 26 96.1
condition 6 6 100.0
subroutine 18 18 100.0
pod 0 3 0.0
total 117 121 96.6


line stmt bran cond sub pod time code
1             package Syntax::Feature::Try;
2              
3 27     27   2728494 use 5.014;
  27         100  
4 27     27   139 use strict;
  27         60  
  27         449  
5 27     27   122 use warnings;
  27         58  
  27         517  
6 27     27   116 use Carp;
  27         50  
  27         1143  
7 27     27   148 use XSLoader;
  27         101  
  27         533  
8 27     27   124 use Scalar::Util qw/ blessed /;
  27         54  
  27         1308  
9              
10             BEGIN {
11 27     27   98 our $VERSION = '1.005';
12 27         23147 XSLoader::load();
13             }
14              
15             my @custom_exception_matchers;
16              
17             sub install {
18 57     57 0 158789 $^H{+HINTKEY_ENABLED} = 1;
19             }
20              
21             sub uninstall {
22 1     1 0 153 $^H{+HINTKEY_ENABLED} = 0;
23             }
24              
25             sub register_exception_matcher {
26 7     7 0 7974 my ($code_ref) = @_;
27              
28 7 100       27 if (ref($code_ref) ne 'CODE') {
29 3         30 croak "Invalid parameter: expected CODE reference.";
30             }
31              
32 4 100       14 if (not grep { $_ == $code_ref } @custom_exception_matchers) {
  5         15  
33 2         7 push @custom_exception_matchers, $code_ref;
34             }
35             }
36              
37             # only for tests:
38 3     3   18 sub _custom_exception_matchers { @custom_exception_matchers }
39              
40             # TODO convert "our" to "my" variables
41             our $is_end_of_block;
42             our $return_values;
43              
44             sub _statement {
45 300     300   350001 my ($try_block, $catch_list, $finally_block) = @_;
46              
47 300         11733 my $stm_handler = bless {finally => $finally_block}, __PACKAGE__;
48              
49 300         11372 local $@;
50 300         11213 local $is_end_of_block;
51 300         12058 my $exception = run_block($stm_handler, $try_block, 1);
52 299 100 100     3444942 if ($exception and $catch_list) {
53 157         38030 my $catch_block = _get_exception_handler($exception, $catch_list);
54 157 100       7923 if ($catch_block) {
55 156         7579 local $is_end_of_block;
56 156         7937 $exception = run_block($stm_handler, $catch_block, 1, $exception);
57             }
58             }
59              
60 299 100       45479 if ($finally_block) {
61 196         11236 delete $stm_handler->{finally};
62 196         11038 local $is_end_of_block;
63 196         11666 run_block($stm_handler, $finally_block);
64             }
65              
66 297 100       11871 if ($exception) {
67 11         57 _rethrow($exception);
68             }
69              
70 286         11249 $return_values = $stm_handler->{return};
71 286         11523 return $stm_handler->{return};
72             }
73              
74             sub DESTROY {
75 300     300   15481 my ($self) = @_;
76 300         11558 local $is_end_of_block;
77 300 100       33597 run_block($self, $self->{finally}) if $self->{finally};
78             }
79              
80             sub _get_exception_handler {
81 157     157   7629 my ($exception, $catch_list) = @_;
82              
83 157         7516 foreach my $item (@{ $catch_list }) {
  157         14921  
84 212         13953 my ($block_ref, @args) = @$item;
85 212 100       13578 return $block_ref if _exception_match_args($exception, @args);
86             }
87             }
88              
89             sub _exception_match_args {
90 212     212   13531 my ($exception, $className) = @_;
91              
92 212 100       13671 return 1 if not defined $className; # without args catch all exceptions
93              
94 189         13519 foreach my $matcher (@custom_exception_matchers) {
95 10         23 my $result = $matcher->($exception, $className);
96 10 100       139 return $result if defined $result;
97             }
98              
99 182 100       14235 if (Moose::Util::TypeConstraints->can('find_type_constraint')) {
100 7         21 my $type = Moose::Util::TypeConstraints::find_type_constraint($className);
101 7 50       607 return $type->check($exception) if $type;
102             }
103              
104 175   100     35222 return blessed($exception) && $exception->isa($className);
105             }
106              
107             sub _rethrow {
108 11     11   52 die (@_);
109             }
110              
111             sub _set_is_end_of_block {
112 320     320   149419 $is_end_of_block = 1;
113             }
114              
115             sub _get_return_value {
116 154     154   5020 my $return = $return_values;
117 154         4624 undef $return_values;
118              
119 154 100       13763 return wantarray ? @$return : $return->[0];
120             }
121              
122             1;
123              
124             __END__
125              
126             =pod
127              
128             =head1 NAME
129              
130             Syntax::Feature::Try - try/catch/finally statement for exception handling
131              
132             =head1 SYNOPSIS
133              
134             use syntax 'try';
135              
136             try {
137             # run this code and handle errors
138             }
139             catch (My::Class::Err $e) {
140             # handle exception based on class "My::Class::Err"
141             }
142             catch ($e) {
143             # handle other exceptions
144             }
145             finally {
146             # cleanup block
147             }
148              
149             =head1 DESCRIPTION
150              
151             This module implements syntax for try/catch/finally statement with behaviour
152             similar to other programming languages (like Java, Python, etc.).
153              
154             It handles correctly return/wantarray inside try/catch/finally blocks.
155              
156             It uses perl keyword/parser API. So it requires B<perl E<gt>= 5.14>.
157              
158             =head1 SYNTAX
159              
160             =head2 initiliazation
161              
162             To initialize this syntax feature call:
163              
164             use syntax 'try';
165              
166             =head2 try
167              
168             The I<try block> is executed.
169             If it throws an error, then first I<catch block> (in order) that can handle
170             thrown error will be executed. Other I<catch blocks> will be skipped.
171              
172             If none of I<catch blocks> can handle the error, it is thrown out of
173             whole statement. If I<try block> does not throw an error,
174             all I<catch blocks> are skipped.
175              
176             =head2 catch error class
177              
178             catch (My::Error $err) { ... }
179              
180             This I<catch block> can handle error that is instance of class C<My::Error>
181             or any of it's subclasses.
182              
183             Caught error is accessible inside I<catch block>
184             via declared local variable C<$err>.
185              
186             =head2 catch all errors
187              
188             To catch all errors use syntax:
189              
190             catch ($e) { ... }
191              
192             Caught error is accessible inside I<catch block>
193             via declared local variable C<$e>.
194              
195             =head2 catch without variable
196              
197             Variable name in catch block is not mandatory:
198              
199             try {
200             ...
201             }
202             catch (MyError::FileNotFound) {
203             print "file not found";
204             }
205             catch {
206             print "operation failed";
207             }
208              
209             =head2 rethrow error
210              
211             To rethrow caught error call "die $err".
212              
213             For example (log any Connection::Error):
214              
215             try { ... }
216             catch (Connection::Error $err) {
217             log_error($err);
218             die $err;
219             }
220              
221             =head2 finally
222              
223             The I<finally block> is executed at the end of statement.
224             It is always executed (even if try or catch block throw an error).
225              
226             my $fh;
227             try {
228             $fh = IO::File->new("/etc/hosts");
229             ...
230             }
231             finally {
232             $fh->close if $fh;
233             }
234              
235             B<WARNING>: If finally block throws an exception,
236             originaly thrown exception (from try/catch block) is discarded.
237             You can convert errors inside finally block to warnings:
238              
239             try {
240             # try block
241             }
242             finally {
243             try {
244             # cleanup code
245             }
246             catch ($e) { warn $e }
247             }
248              
249             =head1 Supported features
250              
251             =head2 Exception::Class
252              
253             This module is compatible with Exception::Class
254              
255             use Exception::Class (
256             'My::Test::Error'
257             );
258             use syntax 'try';
259              
260             try {
261             ...
262             My::Test::Error->throw('invalid password');
263             }
264             catch (My::Test::Error $err) {
265             # handle error here
266             }
267              
268             =head2 Moose::Util::TypeConstraints
269              
270             This module is able to handle subtypes defined using
271             L<Moose::Util::TypeConstraints> (but it does not require to be this package
272             installed if you don't use this feature).
273              
274             use Moose::Util::TypeConstraints;
275              
276             class_type 'Error' => { class => 'My::Error' };
277             subtype 'BillingError', as 'Error', where { $_->category eq 'billing' };
278              
279             try {
280             ...
281             }
282             catch (BillingError $err) {
283             # handle subtype BillingError
284             }
285              
286             =head2 return from subroutine
287              
288             This module supports calling "return" inside try/catch/finally blocks
289             to return values from subroutine.
290              
291             sub read_config {
292             my $file;
293             try {
294             $fh = IO::File->new(...);
295             return $fh->getline; # it returns value from subroutine "read_config"
296             }
297             catch ($e) {
298             # log error
299             }
300             finally {
301             $fh->close() if $fh;
302             }
303             }
304              
305             =head2 using custom exception class matcher
306              
307             There is possible register own subroutine (custom exception matcher)
308             for extending internal className-matcher logic.
309              
310             For example:
311              
312             use syntax 'try';
313              
314             sub is_expected_ref {
315             my ($exception, $className) = @_;
316             my ($expected_ref) = $className =~ /^is_ref::(.+)/;
317              
318             # use default logic if $className is not begning with 'is_ref::'
319             return if not $expected_ref;
320              
321             return ( ref($exception) eq $expected_ref ? 1 : 0 );
322             }
323              
324             Syntax::Feature::Try::register_exception_matcher(\&is_expected_ref);
325              
326             ...
327              
328             try { ... }
329             catch (is_ref::CODE) {
330             # there is handled any exception that is CODE-reference,
331             # because custom exception matcher returns 1 in this case
332             }
333              
334             Exception matcher subroutine has two arguments:
335             first ($exception) is tested exception,
336             second ($className) is className expected in "catch block".
337             It should return undef if given $className cannon be handled by exception matcher
338             (in this case next registered matchers or default matcher is executed)
339             otherwise return 1 or 0 as result of your own match $exception to $className.
340              
341             Note that multiple custom matchers may be registered.
342              
343             =head1 CAVEATS
344              
345             =head2 @_
346              
347             C<@_> is not accessible inside try/catch/finally blocks,
348             because these blocks are internally called in different context.
349              
350             =head2 next, last, redo
351              
352             C<next>, C<last> and C<redo> is not working inside try/catch/finally blocks,
353             because these blocks are internally called in different context.
354              
355             =head2 goto
356              
357             C<goto> can't be used to get out of a try, catch or finally block.
358              
359             =head1 BUGS
360              
361             None bugs known.
362              
363             =head1 SEE ALSO
364              
365             L<syntax> - Active syntax extensions
366              
367             L<Exception::Class> - A module that allows you to declare real exception
368             classes in Perl
369              
370             L<Moose::Util::TypeConstraints>
371              
372             =head2 Other similar packages
373              
374             L<TryCatch>
375              
376             =over
377              
378             =item *
379              
380             It reports wrong line numbers from warn/die calls inside try/catch blocks.
381              
382             =item *
383              
384             It does not support "finally" block.
385              
386             =item *
387              
388             It works on perl E<lt> 5.14
389              
390             =back
391              
392             L<Try>
393              
394             =over
395              
396             =item *
397              
398             It does not support catching errors by their ISA (i.e. it has only one catch block that takes all errors and you must write additinal if/else code to rethrow other exceptions).
399              
400             =back
401              
402             L<Try::Tiny>
403              
404             =over
405              
406             =item *
407              
408             It does not support catching errors by their ISA (i.e. it has only one catch block that takes all errors and you must write additinal if/else code to rethrow other exceptions).
409              
410             =item *
411              
412             It generates expression (instead of statement), i.e. it requires semicolon after last block. Missing semicolon before or after try/catch expression may be hard to debug (it is not always reported as syntax error).
413              
414             =item *
415              
416             It works on perl E<lt> 5.14 (It is written in pure perl).
417              
418             =back
419              
420             =head1 GIT REPOSITORY
421              
422             L<http://github.com/tomas-zemres/syntax-feature-try>
423              
424             =head1 AUTHOR
425              
426             Tomas Pokorny <tnt at cpan dot org>
427              
428             =head1 COPYRIGHT AND LICENCE
429              
430             Copyright 2013 - Tomas Pokorny.
431              
432             This program is free software;
433             you can redistribute it and/or modify it under the same terms as Perl itself.
434              
435             =for Pod::Coverage HINTKEY_ENABLED install uninstall run_block register_exception_matcher
436              
437             =cut