File Coverage

blib/lib/Combinator.pm
Criterion Covered Total %
statement 62 63 98.4
branch 18 20 90.0
condition 11 11 100.0
subroutine 13 13 100.0
pod 1 6 16.6
total 105 113 92.9


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