ProtoThreads.pm 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. # Perl Protothreads Version 1.04
  2. #
  3. # a lightwight pseudo-threading framework for perl that is
  4. # heavily inspired by Adam Dunkels protothreads for the c-language
  5. #
  6. # LICENSE AND COPYRIGHT
  7. #
  8. # Copyright (C) 2014 ntruchsess (norbert.truchsess@t-online.de)
  9. #
  10. # This program is free software; you can redistribute it and/or modify it
  11. # under the terms of either: the GNU General Public License as published
  12. # by the Free Software Foundation; or the Artistic License.
  13. #
  14. # See http://dev.perl.org/licenses/ for more information.
  15. #
  16. #PT_THREAD(sub)
  17. #Declare a protothread
  18. #
  19. #PT_INIT(thread)
  20. #Initialize a thread
  21. #
  22. #PT_BEGIN(thread);
  23. #Declare the start of a protothread inside the sub implementing the protothread.
  24. #
  25. #PT_WAIT_UNTIL(condition);
  26. #Block and wait until condition is true.
  27. #
  28. #PT_WAIT_WHILE(condition);
  29. #Block and wait while condition is true.
  30. #
  31. #PT_WAIT_THREAD(thread);
  32. #Block and wait until another protothread completes.
  33. #
  34. #PT_SPAWN(thread);
  35. #Spawn a child protothread and wait until it exits.
  36. #
  37. #PT_RESTART;
  38. #Restart the protothread.
  39. #
  40. #PT_EXIT;
  41. #Exit the protothread. Use PT_EXIT(value) to pass an exit-value to PT_EXITVAL
  42. #
  43. #PT_END;
  44. #Declare the end of a protothread.
  45. #
  46. #PT_SCHEDULE(protothread);
  47. #Schedule a protothread.
  48. #
  49. #PT_YIELD;
  50. #Yield from the current protothread.
  51. #
  52. #PT_YIELD_UNTIL(condition);
  53. #Yield from the current protothread until the condition is true.
  54. #
  55. #PT_RETVAL
  56. #return the value that has been (optionaly) passed by PT_EXIT(value)
  57. package ProtoThreads;
  58. use constant {
  59. PT_INITIAL => 0,
  60. PT_WAITING => 1,
  61. PT_YIELDED => 2,
  62. PT_EXITED => 3,
  63. PT_ENDED => 4,
  64. PT_ERROR => 5,
  65. PT_CANCELED => 6,
  66. };
  67. my $DEBUG=0;
  68. use Exporter 'import';
  69. @EXPORT = qw(PT_THREAD PT_INITIAL PT_WAITING PT_YIELDED PT_EXITED PT_ENDED PT_ERROR PT_CANCELED PT_INIT PT_SCHEDULE);
  70. @EXPORT_OK = qw();
  71. use Text::Balanced qw (
  72. extract_codeblock
  73. );
  74. sub PT_THREAD($) {
  75. my $method = shift;
  76. return bless({
  77. PT_THREAD_STATE => PT_INITIAL,
  78. PT_THREAD_POSITION => 0,
  79. PT_THREAD_METHOD => $method
  80. }, "ProtoThreads");
  81. }
  82. sub PT_INIT($) {
  83. my $self = shift;
  84. $self->{PT_THREAD_POSITION} = 0;
  85. $self->{PT_THREAD_STATE} = PT_INITIAL;
  86. delete $self->{PT_THREAD_ERROR};
  87. }
  88. sub PT_SCHEDULE(@) {
  89. my ($self) = @_;
  90. my $state = $self->{PT_THREAD_METHOD}(@_);
  91. return ($state == PT_WAITING or $state == PT_YIELDED);
  92. }
  93. sub PT_CANCEL($) {
  94. my ($self,$cause) = @_;
  95. $self->{PT_THREAD_POSITION} = 0;
  96. $self->{PT_THREAD_ERROR} = $cause;
  97. $self->{PT_THREAD_STATE} = PT_CANCELED;
  98. }
  99. sub PT_RETVAL() {
  100. my $self = shift;
  101. return $self->{PT_THREAD_RETURN};
  102. }
  103. sub PT_STATE() {
  104. my $self = shift;
  105. return $self->{PT_THREAD_STATE};
  106. }
  107. sub PT_CAUSE() {
  108. my $self = shift;
  109. return $self->{PT_THREAD_ERROR};
  110. }
  111. sub PT_NEXTCOMMAND($$) {
  112. my ($code,$command) = @_;
  113. if ($code =~ /$command\s*(?=\()/s) {
  114. if ($') {
  115. my $before = $`;
  116. my $after = $';
  117. my ($match,$remains,$prefix) = extract_codeblock($after,"()");
  118. $match =~ /(^\()(.*)(\)$)/;
  119. my $arg = $2 if defined $2;
  120. $remains =~ s/^\s*;//sg;
  121. return (1,$before,$arg,$remains);
  122. }
  123. }
  124. return undef;
  125. }
  126. use Filter::Simple;
  127. FILTER_ONLY
  128. executable => sub {
  129. my $code = $_;
  130. my $counter = 1;
  131. my ($success,$before,$arg,$after,$beforeblock);
  132. while(1) {
  133. ($success,$beforeblock,$arg,$after) = PT_NEXTCOMMAND($code,"PT_BEGIN");
  134. if ($success) {
  135. if ($after =~ /PT_END\s*;/s) {
  136. my $thread = $arg;
  137. my $block = $thread."->{PT_THREAD_STATE} = eval { my \$PT_YIELD_FLAG = 1; goto ".$thread."->{PT_THREAD_POSITION} if ".$thread."->{PT_THREAD_POSITION};".$`.$thread."->{PT_THREAD_POSITION} = 0; delete ".$thread."->{PT_THREAD_RETURN}; return PT_ENDED; }; if (\$\@) {".$thread."->{PT_THREAD_STATE} = PT_ERROR; ".$thread."->{PT_THREAD_ERROR} = \$\@; }; return ".$thread."->{PT_THREAD_STATE};";
  138. my $afterblock = $';
  139. while (1) {
  140. ($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_YIELD_UNTIL");
  141. if ($success) {
  142. $block=$before."\$PT_YIELD_FLAG = 0; ".$thread."->{PT_THREAD_POSITION} = 'PT_LABEL_$counter'; PT_LABEL_$counter: return PT_YIELDED unless (\$PT_YIELD_FLAG and ($arg));".$after;
  143. $counter++;
  144. next;
  145. }
  146. if ($block =~ /PT_YIELD\s*;/s) {
  147. $block = $`."\$PT_YIELD_FLAG = 0; ".$thread."->{PT_THREAD_POSITION} = 'PT_LABEL_$counter'; PT_LABEL_$counter: return PT_YIELDED unless \$PT_YIELD_FLAG;".$';
  148. $counter++;
  149. next;
  150. }
  151. ($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_WAIT_UNTIL");
  152. if ($success) {
  153. $block=$before.$thread."->{PT_THREAD_POSITION} = 'PT_LABEL_$counter'; PT_LABEL_$counter: return PT_WAITING unless ($arg);".$after;
  154. $counter++;
  155. next;
  156. }
  157. ($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_WAIT_WHILE");
  158. if ($success) {
  159. $block=$before.$thread."->{PT_THREAD_POSITION} = 'PT_LABEL_$counter'; PT_LABEL_$counter: return PT_WAITING if ($arg);".$after;
  160. $counter++;
  161. next;
  162. }
  163. ($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_WAIT_THREAD");
  164. if ($success) {
  165. $block=$before."PT_WAIT_WHILE(PT_SCHEDULE(".$arg."));".$after;
  166. next;
  167. }
  168. ($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_SPAWN");
  169. if ($success) {
  170. $block=$before.$arg."->{PT_THREAD_POSITION} = 0; PT_WAIT_THREAD($arg);".$after;
  171. next;
  172. }
  173. ($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_EXIT");
  174. if ($success) {
  175. $block=$before.$thread."->{PT_THREAD_POSITION} = 0; ".$thread."->{PT_THREAD_RETURN} = $arg; return PT_EXITED;".$after;
  176. next;
  177. }
  178. if ($block =~ /PT_EXIT(\s*;|\s+)/s) {
  179. $block = $`.$thread."->{PT_THREAD_POSITION} = 0; delete ".$thread."->{PT_THREAD_RETURN}; return PT_EXITED".$1.$';
  180. next;
  181. }
  182. if ($block =~ /PT_RESTART(\s*;|\s)/s) {
  183. $block = $`.$thread."->{PT_THREAD_POSITION} = 0; return PT_WAITING;".$1.$';
  184. next;
  185. }
  186. last;
  187. }
  188. $code = $beforeblock.$block.$afterblock;
  189. } else {
  190. die "PT_END expected"
  191. }
  192. next;
  193. }
  194. last;
  195. };
  196. print $code if $DEBUG;
  197. $_ = $code;
  198. };
  199. 1;