File Coverage

blib/lib/Combinator.pm
Criterion Covered Total %
statement 63 64 98.4
branch 16 18 88.8
condition 2 2 100.0
subroutine 14 14 100.0
pod 1 6 16.6
total 96 104 92.3


line stmt bran cond sub pod time code
1             package Combinator;
2              
3 6     6   232712 use 5.010;
  6         26  
  6         254  
4 6     6   38 use strict;
  6         11  
  6         200  
5 6     6   34 use warnings;
  6         14  
  6         195  
6              
7             =head1 NAME
8              
9             Combinator - Intuitively write async program serially, parallel, or circularly
10              
11             =head1 VERSION
12              
13             Version 0.4.2
14              
15             =cut
16              
17 6     6   6580 use version;
  6         14516  
  6         37  
18             our $VERSION = qv 'v0.4.2';
19              
20             =head1 SYNOPSIS
21              
22             The following is the basic form for serializing a sequence of async code blocks:
23              
24             use Combinator;
25             use AE;
26              
27             my $cv = AE::cv;
28             {{com
29             print "sleep 1 second\n";
30             my $t = AE::timer 1, 0, {{next}};
31             --ser
32             undef $t;
33             my $t = AE::timer 0.5, 0, {{next}};
34             print "sleep 0.5 second\n"; # this line will be executed before the next block
35             --ser
36             undef $t;
37             print "wait for 3 timers at the same time\n";
38             my $t1 = AE::timer 1, 0, {{next}};
39             my $t2 = AE::timer 2, 0, {{next}};
40             my $t3 = AE::timer 1.5, 0, {{next}};
41             --ser
42             undef $t1; undef $t2; undef $t3;
43             # after the max time interval of them (2 seconds)
44             print "the next block will start immediately\n";
45             --ser
46             print "done\n";
47             $cv->send;
48             }}com
49             $cv->recv;
50              
51             The following block will wait for previous block's end and all the {{next}}s in the
52             previous block been called.
53              
54             And also, it could be nested {{com..}}com blocks in the code block.
55             the following block will also wait for completion of these {{com..}}com blocks.
56             Thus, you can distribute independent code blocks into each one,
57             and optionally use 'return' to stop the {{com..}}com block.
58              
59             use Combinator;
60             use AE;
61              
62             my $cv = AE::cv;
63             {{com
64             print "all start\n";
65             {{com
66             print "A begin\n";
67             my $t = AE::timer 1, 0, {{next}};
68             --ser
69             undef $t;
70             print "A second\n";
71             my $t = AE::timer 1, 0, {{next}};
72             --ser
73             undef $t;
74             print "A done\n";
75             return; # this will stop the later part of this {{com..}}com block
76             --ser
77             print "never be here\n";
78             --ser
79             print "never be here either\n";
80             }}com
81              
82             {{com
83             print "B begin\n";
84             my $t = AE::timer .7, 0, {{next}};
85             --ser
86             print "B second\n";
87             my $t = AE::timer .7, 0, {{next}};
88             --ser
89             print "B done\n";
90             --com # this is a short cut for }}com {{com
91             print "C begin\n";
92             my $t = AE::timer .4, 0, {{next}};
93             --ser
94             print "C second\n";
95             my $t = AE::timer .4, 0, {{next}};
96             --ser
97             print "C done\n";
98             }}com
99             --ser
100             print "all done\n";
101             $cv->send;
102             }}com
103             $cv->recv;
104              
105             And also, the following block will get all the arguments when {{next}} is called.
106             This is useful when integrating with other callback based module.
107              
108             use Combinator;
109             use AE;
110             use AnyEvent::HTTP;
111              
112             my $cv = AE::cv;
113             {{com
114             print "start\n";
115             http_get "http://search.cpan.org/", {{next}};
116             --ser
117             my($data, $headers) = @_; # the cb args of http_get
118              
119             if( !defined($data) ) {
120             print "Fetch cpan fail\n";
121             return;
122             }
123             print "Fetch cpan success\n";
124              
125             http_get "http://www.perl.org/", {{next}};
126             --ser
127             my($data, $headers) = @_; # the cb args of http_get
128              
129             if( !defined($data) ) {
130             print "Fetch perl fail\n";
131             return;
132             }
133             print "Fetch perl success\n";
134              
135             print "done\n";
136             $cv->send;
137             }}com
138             $cv->recv;
139              
140             If there are multiple {{next}}s been called,
141             You'll get all the args concatenated together.
142              
143             use Combinator;
144             use AE;
145              
146             my $cv = AE::cv;
147             {{com
148             {{next}}->(0);
149             {{com
150             my $t = AE::timer 1, 0, {{next}};
151             --ser
152             undef $t;
153             {{next}}->(1);
154             --com
155             my $t = AE::timer .6, 0, {{next}};
156             --ser
157             undef $t;
158             {{next}}->(2);
159             --com
160             my $t = AE::timer .3, 0, {{next}};
161             --ser
162             undef $t;
163             {{next}}->(3);
164             }}com
165             {{next}}->(4);
166             --ser
167             print "@_\n"; # 0 4 3 2 1
168             $cv->send;
169             }}com
170              
171             If you want to process each {{next}}'s args seperately,
172             you might use seperate {{com..}}com, and then gather the final result.
173              
174             use Combinator;
175             use AnyEvent::HTTP;
176             use Data::Dumper;
177              
178             my $cv = AE::cv;
179             {{com
180             my @health;
181             for my $url (qw(http://www.perl.org/ http://search.cpan.org/)) {{com
182             my $url = $url; # we need to copy-out the $url here,
183             # or the later part of the {{com..}}com will
184             # not get the correct one.
185             http_get $url, {{next}};
186             --ser
187             push @health, [$url, defined($_[0])];
188             }}com
189             --ser
190             print Dumper(\@health);
191             $cv->send;
192             }}com
193              
194             If you wish to run a {{com..}}com repeatly. Use {{cir instead of {{com,
195             or use --cir instead of --com if it's not the first block.
196              
197             use Combinator;
198             use AE;
199             use AnyEvent::Socket;
200             use AnyEvent::Handle;
201              
202             tcp_server 0, 8888, sub {
203             my($fh, $host, $port) = @_;
204              
205             my $hd; $hd = AnyEvent::Handle->new(
206             fh => $fh,
207             on_error => sub {
208             print "socket $host:$port end.\n";
209             undef $hd;
210             },
211             );
212              
213             {{cir
214             $hd->push_read( line => {{next}} );
215             --ser
216             my($hd, $line) = @_;
217             $hd->push_write($line.$/);
218             }}com
219             };
220              
221             AE::cv->recv;
222              
223             If you need finer controlled {{next}}, use {{nex .. }}nex block to
224             replace {{next}}.
225              
226             use Combinator;
227             use AE;
228             use AnyEvent::HTTP;
229              
230             {{com
231             my($a_res, $b_res);
232             http_get 'http://site.a/', {{nex $a_res = $_[1] }}nex;
233             http_get 'http://site.b/', {{nex $b_res = $_[1] }}nex;
234             --ser
235             print "Completed!\n";
236             print "SiteA = $a_res\n";
237             print "SiteB = $b_res\n";
238             }}com
239              
240             AE::cv->recv;
241              
242             Though without {{nex .. }}nex block, you can still write:
243              
244             use Combinator;
245             use AE;
246             use AnyEvent::HTTP;
247              
248             {{com
249             my($a_res, $b_res);
250             {{com
251             http_get 'http://site.a/', {{next}};
252             --ser
253             $a_res = $_[1];
254             --com
255             http_get 'http://site.b/', {{next}};
256             --ser
257             $b_res = $_[1];
258             }}com
259             --ser
260             print "Completed!\n";
261             print "SiteA = $a_res\n";
262             print "SiteB = $b_res\n";
263             }}com
264              
265             AE::cv->recv;
266              
267             It's up to you to choose which one to use.
268              
269             =head1 WHEN YOU SHOULD USE THIS MODULE
270              
271             =head2 When you are tired of writing layered closures
272              
273             use AnyEvent::DBI;
274              
275             ...
276              
277             $dbh->exec("select ...", sub {
278             ...
279             $dbh->exec("select ...", sub {
280             ...
281             $dbh->exec("select ...", sub {
282             ...
283             $dbh->exec("select ...", sub {
284             ...
285             });
286             });
287             });
288             });
289              
290             You can achieve that like this:
291              
292             use Combinator;
293             use AnyEvent::DBI;
294              
295             ...
296              
297             {{com
298             $dbh->exec("select ...", {{next}});
299             ...
300             --ser
301             $dbh->exec("select ...", {{next}});
302             ...
303             --ser
304             $dbh->exec("select ...", {{next}});
305             ...
306             --ser
307             $dbh->exec("select ...", {{next}});
308             ...
309             }}com
310              
311             =head2 When you are tired of manually using condition variable to
312             achieve asynchronous concurrent program.
313              
314             use AE;
315              
316             ...
317              
318             AE::io $fh, 0, sub {
319             my($file_a, $file_b);
320             my $cv = AE::cv {
321             my $cv2 = AE::cv {
322             sock_send($admin, "done", sub{});
323             };
324             $cv2->begin;
325             for(@user) {
326             sock_send($_, $file_a.$file_b, sub { $cv2->end });
327             }
328             $cv2->end;
329             };
330              
331             $cv->begin;
332              
333             $cv->begin;
334             read_a_file(..., sub { $file_a = ...; $cv->end });
335             $cv->begin;
336             read_a_file(..., sub { $file_b = ...; $cv->end });
337              
338             $cv->end;
339             };
340              
341             You can achieve that like this:
342              
343             use Combinator;
344             use AE;
345              
346             ...
347              
348             AE::io $fh, 0, sub {{com
349             my($file_a, $file_b);
350             {{com
351             read_a_file(..., {{next}});
352             --ser
353             $file_a = ...;
354             --com
355             read_a_file(..., {{next}});
356             --ser
357             $file_b = ...;
358             }}com
359             --ser
360             for(@user) {
361             sock_send($_, $file_a.$file_b, {{next}});
362             }
363             --ser
364             sock_send($admin, "done", {{next}});
365             }}com
366              
367             =head2 When you are afraid of using recursion to achieve LOOP
368             in an event-driven program.
369              
370             use AE;
371              
372             ...
373              
374             sub sooner {
375             my $int = shift;
376             print "$int\n";
377             return if $int <= 0;
378             my $t = AE::timer $int, 0, sub {
379             undef $t;
380             sooner($int-1);
381             };
382             }
383             sooner(3);
384              
385             You can achieve that like this:
386              
387             use AE;
388              
389             ...
390              
391             sub sooner {{com
392             my $int = shift;
393             my $t;
394             {{cir
395             print "$int\n";
396             if( $int <= 0 ) {
397             undef $t;
398             return;
399             }
400             $t = AE::timer $int, 0, {{next}};
401             --$int;
402             }}com
403             }}com
404             sooner(3);
405              
406             =head1 OPTIONS
407              
408             You can set some options like this:
409              
410             use Combinator verbose => 1, begin => qr/\{\{COM\b/;
411              
412             Possible options are:
413              
414             =head2 verbose => 0
415              
416             Set to 1 if you want to see the generated code.
417              
418             =head2 begin => qr/\{\{com\b/
419              
420             =head2 cir_begin => qr/\{\{cir\b/
421              
422             =head2 nex_begin => qr/\{\{nex\b/
423              
424             =head2 ser => qr/--ser\b/
425              
426             =head2 par => qr/--com\b/
427              
428             =head2 cir_par => qr/--cir\b/
429              
430             =head2 end => qr/\}\}(?:com|cir|nex)\b/
431              
432             =head2 next => qr/\{\{next\}\}/
433              
434             You can change these patterns to what you want
435              
436             =head1 CAVEATS
437              
438             =head2 PATTERNS IN COMMENTS OR STRINGS
439              
440             This module is implemented by filter your code directly.
441             So it will still take effect if the pattern ({{com, {{next}, ... etc)
442             show up in comments or strings. So avoid it!
443              
444             You may use options listed above to change the default patterns.
445              
446             =head2 INFINITE RECURSION
447              
448             The {{cir or --cir is implemented by recursion.
449             That is, if you using that without going through any event loop,
450             it may result in infinite recursion.
451              
452             You can avoid that by a zero time timer. For example:
453              
454             {{cir
455             print "Go\n";
456             }}com
457              
458             This will crash immediately due to the deep recursion.
459             You can replace it by:
460              
461             {{cir
462             print "Go\n";
463             my $t; $t = AE::timer 0, 0, {{next}};
464             --ser
465             undef $t;
466             }}com
467              
468             =head2 LATE STARTED NEXT
469              
470             Each serial block will start to run once the previous block
471             is finished and all the started {{next}}s have been called.
472             That is, the un-started {{next}} is not counted.
473              
474             Here's an example:
475              
476             {{com
477             my $t; $t = AE::timer 1, 0, sub {
478             undef $t;
479             print "A\n";
480             {{next}}->();
481             };
482             --ser
483             print "B\n";
484             }}com
485              
486             It'll print "B" before "A", cause when the later block
487             is checking if the previous one is finished, the {{next}}
488             in the timer callback hasn't started.
489              
490             You can fix it by:
491              
492             {{com
493             my $next = {{next}};
494             my $t; $t = AE::timer 1, 0, sub {
495             undef $t;
496             print "A\n";
497             $next->();
498             };
499             --ser
500             print "B\n";
501             }}com
502              
503             Then "B" will come after "A";
504              
505             =head1 DEMO
506              
507             Look up the file eg/demo_all.pl
508              
509             =cut
510              
511              
512             =head1 AUTHOR
513              
514             Cindy Wang (CindyLinz)
515              
516             =head1 BUGS
517              
518             Please report any bugs or feature requests to github L.
519             I will be notified, and then you'll
520             automatically be notified of progress on your bug as I make changes.
521              
522              
523              
524              
525             =head1 SUPPORT
526              
527             You can find documentation for this module with the perldoc command.
528              
529             perldoc Combinator
530              
531              
532             You can also look for information at:
533              
534             =over 4
535              
536             =item * github:
537              
538             L
539              
540             =item * AnnoCPAN: Annotated CPAN documentation
541              
542             L
543              
544             =item * CPAN Ratings
545              
546             L
547              
548             =item * Search CPAN
549              
550             L
551              
552             =back
553              
554              
555             =head1 LICENSE AND COPYRIGHT
556              
557             Copyright 2011 Cindy Wang (CindyLinz).
558              
559             This program is free software; you can redistribute it and/or modify it
560             under the terms of either: the GNU General Public License as published
561             by the Free Software Foundation; or the Artistic License.
562              
563             See http://dev.perl.org/licenses/ for more information.
564              
565              
566             =cut
567              
568 6     6   13248 use Filter::Simple;
  6         301553  
  6         51  
569 6     6   30088 use Guard;
  6         4316  
  6         475  
570 6     6   16090 use Devel::Caller;
  6         37227  
  6         7418  
571              
572             my %opt;
573             my $begin_pat;
574             my $end_pat;
575             my $cir_begin_pat;
576             my $ser_pat;
577             my $par_pat;
578             my $cir_par_pat;
579             my $com_pat;
580             my $token_pat;
581             my $nex_begin_pat;
582             my $line_shift;
583              
584             our $cv1 = [];
585              
586             sub import {
587             my $self = shift;
588             %opt = (
589             verbose => 0,
590             begin => qr/\{\{com\b/,
591             cir_begin => qr/\{\{cir\b/,
592             nex_begin => qr/\{\{nex\b/,
593             ser => qr/--ser\b/,
594             par => qr/--com\b/,
595             cir_par => qr/--cir\b/,
596             end => qr/\}\}(?:com|cir|nex)\b/,
597             next => qr/\{\{next\}\}/,
598             @_
599             );
600             $begin_pat = qr/$opt{begin}|$opt{cir_begin}|(?:$opt{nex_begin})/;
601             $end_pat = $opt{end};
602             $ser_pat = $opt{ser};
603             $par_pat = $opt{par};
604             $cir_begin_pat = $opt{cir_begin};
605             $nex_begin_pat = $opt{nex_begin};
606             $cir_par_pat = $opt{cir_par};
607             $com_pat = qr/($begin_pat((?:(?-2)|(?!$begin_pat).)*?)$end_pat)/s;
608             $token_pat = qr/$com_pat|(?!$begin_pat)./s;
609             $line_shift = (caller)[2];
610             }
611              
612             sub att_sub {
613 38     38 0 2078 my($att1, $att2, $cb) = @_;
614             sub {
615 38     38   92 unshift @_, $att1, $att2;
616 38         86 &$cb;
617             }
618 38         212 }
619              
620             # $cv = [wait_count, cb, args]
621             sub cv_end { # (cv, args)
622 27     27 0 356 --$_[0][0];
623 27 50 100     72 push @{$_[0][2]//=[]}, @{$_[1]} if $_[1];
  27         176  
  27         48  
624 27 100       110 if( !$_[0][0] ) {
625 9 100       32 if( $_[0][1] ) {
626 4         5 delete($_[0][1])->(@{$_[0][2]});
  4         23  
627             }
628 9         47 undef $_[0][2];
629             }
630             }
631             sub cv_cb { # (cv, cb)
632 30 50   30 0 87 if( $_[0][0] ) {
633 0         0 $_[0][1] = $_[1];
634             }
635             else {
636 30         42 $_[1](@{$_[0][2]});
  30         111  
637 30         86 undef $_[0][2];
638             }
639             }
640              
641             sub ser {
642 47     47 1 63 my $depth = shift;
643 47 100       152 if( @_ <= 1 ) { # next only
644 19         47 return $_[0];
645             }
646 28         45 my $code = shift;
647 28         40 unshift @_, $depth;
648 28         71 my $next = &ser;
649 28         75 replace_code($depth, $code);
650 28         106 $code =~ s/$opt{next}/(do{my\$t=\$Combinator::cv1;++\$t->[0];sub{if(\$t){Combinator::cv_end(\$t,\\\@_);undef\$t}else{my(undef,\$f,\$l)=caller;warn"next should be invoked only once at \$f line \$l.\\n"}}})/g;
651 28         104 my $out = "local\$Combinator::guard=Guard::guard{Combinator::cv_end(\$Combinator::cv0,\\\@_)};local\$Combinator::cv1=[1];$code;--\$Combinator::cv1->[0];Combinator::cv_cb(\$Combinator::cv1,Combinator::att_sub(\$Combinator::head,\$Combinator::cv0,sub{local\$Combinator::head=shift;local\$Combinator::cv0=shift;$next}));\$Combinator::guard->cancel";
652 28         118 return $out;
653             }
654              
655             sub com { # depth, code, head
656 19     19 0 45 my($depth, $code, $head) = @_;
657 19         160 my @ser;
658 19 100       73 $code .= "\n" if( substr($code, -1) eq "\n" );
659 19         1615 push @ser, $1 while( $code =~ m/(?:^|$ser_pat)($token_pat*?)(?=$ser_pat|$)/gs );
660              
661 19         84 my $delayed = $head =~ $nex_begin_pat;
662              
663 19 100       274 my $out = (
    100          
    100          
664             $delayed ?
665             "(do{++\$Combinator::cv1->[0];Combinator::att_sub(do{\\(my\$t=1)},\$Combinator::cv1,sub{if(!\${\$_[0]}){my(undef,\$f,\$l)=caller;warn\"nex should be invoked only once at \$f line \$l.\\n\";return}--\${\$_[0]};shift;local\$Combinator::cv0=shift;" :
666             "{&{sub{local\$Combinator::cv0=\$Combinator::cv1;++\$Combinator::cv0->[0];"
667             )."local\$Combinator::head=[1,Devel::Caller::caller_cv(0)];" .
668             ser($depth+1, @ser, $head =~ /^(?:$cir_par_pat|$cir_begin_pat)$/ ? "--\$Combinator::cv0->[0];\$Combinator::cv1=\$Combinator::cv0;Combinator::cv_end(\$Combinator::head,\\\@_)" : "Combinator::cv_end(\$Combinator::cv0,\\\@_)") .
669             (
670             $delayed ?
671             "})})" :
672             "}}}"
673             );
674 19         366 return $out;
675             }
676              
677             sub replace_code {
678 34     34 0 64 my $depth = shift;
679 34         1430 $_[0] =~ s[$com_pat]{
680 16         51 my $code = $1;
681 16         25 my $out = '';
682 16         1605 while( $code =~ /($begin_pat|$par_pat|$cir_par_pat)($token_pat*?)(?=($par_pat|$cir_par_pat|$end_pat))/g ) {
683 19         62 my $fragment = $2;
684 19         56 $out .= com($depth, $fragment, $1);
685             }
686 16         144 $out;
687             }ge;
688             }
689              
690             FILTER {
691             replace_code(0, $_);
692             if( $opt{verbose} ) {
693             my $verbose_code = $_;
694             my $n = $line_shift;
695             $verbose_code =~ s/^/sprintf"%6d: ", ++$n/gem;
696             print "Code after filtering:\n$verbose_code\nEnd Of Code\n";
697             }
698             };
699              
700             1; # End of Combinator