mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-04 22:19:38 +00:00

Merge branch 'owx_protothreads' git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@6259 2b470e98-0d58-463d-a4d8-8e2adae1ed80
221 lines
6.3 KiB
Perl
221 lines
6.3 KiB
Perl
# Perl Protothreads Version 1.04
|
|
#
|
|
# a lightwight pseudo-threading framework for perl that is
|
|
# heavily inspired by Adam Dunkels protothreads for the c-language
|
|
#
|
|
# LICENSE AND COPYRIGHT
|
|
#
|
|
# Copyright (C) 2014 ntruchsess (norbert.truchsess@t-online.de)
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it
|
|
# under the terms of either: the GNU General Public License as published
|
|
# by the Free Software Foundation; or the Artistic License.
|
|
#
|
|
# See http://dev.perl.org/licenses/ for more information.
|
|
#
|
|
#PT_THREAD(sub)
|
|
#Declare a protothread
|
|
#
|
|
#PT_INIT(thread)
|
|
#Initialize a thread
|
|
#
|
|
#PT_BEGIN(thread);
|
|
#Declare the start of a protothread inside the sub implementing the protothread.
|
|
#
|
|
#PT_WAIT_UNTIL(condition);
|
|
#Block and wait until condition is true.
|
|
#
|
|
#PT_WAIT_WHILE(condition);
|
|
#Block and wait while condition is true.
|
|
#
|
|
#PT_WAIT_THREAD(thread);
|
|
#Block and wait until another protothread completes.
|
|
#
|
|
#PT_SPAWN(thread);
|
|
#Spawn a child protothread and wait until it exits.
|
|
#
|
|
#PT_RESTART;
|
|
#Restart the protothread.
|
|
#
|
|
#PT_EXIT;
|
|
#Exit the protothread. Use PT_EXIT(value) to pass an exit-value to PT_EXITVAL
|
|
#
|
|
#PT_END;
|
|
#Declare the end of a protothread.
|
|
#
|
|
#PT_SCHEDULE(protothread);
|
|
#Schedule a protothread.
|
|
#
|
|
#PT_YIELD;
|
|
#Yield from the current protothread.
|
|
#
|
|
#PT_YIELD_UNTIL(condition);
|
|
#Yield from the current protothread until the condition is true.
|
|
#
|
|
#PT_RETVAL
|
|
#return the value that has been (optionaly) passed by PT_EXIT(value)
|
|
|
|
package ProtoThreads;
|
|
|
|
use constant {
|
|
PT_INITIAL => 0,
|
|
PT_WAITING => 1,
|
|
PT_YIELDED => 2,
|
|
PT_EXITED => 3,
|
|
PT_ENDED => 4,
|
|
PT_ERROR => 5,
|
|
PT_CANCELED => 6,
|
|
};
|
|
|
|
my $DEBUG=0;
|
|
|
|
use Exporter 'import';
|
|
@EXPORT = qw(PT_THREAD PT_INITIAL PT_WAITING PT_YIELDED PT_EXITED PT_ENDED PT_ERROR PT_CANCELED PT_INIT PT_SCHEDULE);
|
|
@EXPORT_OK = qw();
|
|
|
|
use Text::Balanced qw (
|
|
extract_codeblock
|
|
);
|
|
|
|
sub PT_THREAD($) {
|
|
my $method = shift;
|
|
return bless({
|
|
PT_THREAD_STATE => PT_INITIAL,
|
|
PT_THREAD_POSITION => 0,
|
|
PT_THREAD_METHOD => $method
|
|
}, "ProtoThreads");
|
|
}
|
|
|
|
sub PT_INIT($) {
|
|
my $self = shift;
|
|
$self->{PT_THREAD_POSITION} = 0;
|
|
$self->{PT_THREAD_STATE} = PT_INITIAL;
|
|
delete $self->{PT_THREAD_ERROR};
|
|
}
|
|
|
|
sub PT_SCHEDULE(@) {
|
|
my ($self) = @_;
|
|
my $state = $self->{PT_THREAD_METHOD}(@_);
|
|
return ($state == PT_WAITING or $state == PT_YIELDED);
|
|
}
|
|
|
|
sub PT_CANCEL($) {
|
|
my ($self,$cause) = @_;
|
|
$self->{PT_THREAD_POSITION} = 0;
|
|
$self->{PT_THREAD_ERROR} = $cause;
|
|
$self->{PT_THREAD_STATE} = PT_CANCELED;
|
|
}
|
|
|
|
sub PT_RETVAL() {
|
|
my $self = shift;
|
|
return $self->{PT_THREAD_RETURN};
|
|
}
|
|
|
|
sub PT_STATE() {
|
|
my $self = shift;
|
|
return $self->{PT_THREAD_STATE};
|
|
}
|
|
|
|
sub PT_CAUSE() {
|
|
my $self = shift;
|
|
return $self->{PT_THREAD_ERROR};
|
|
}
|
|
|
|
sub PT_NEXTCOMMAND($$) {
|
|
my ($code,$command) = @_;
|
|
if ($code =~ /$command\s*(?=\()/s) {
|
|
if ($') {
|
|
my $before = $`;
|
|
my $after = $';
|
|
my ($match,$remains,$prefix) = extract_codeblock($after,"()");
|
|
$match =~ /(^\()(.*)(\)$)/;
|
|
my $arg = $2 if defined $2;
|
|
$remains =~ s/^\s*;//sg;
|
|
return (1,$before,$arg,$remains);
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
use Filter::Simple;
|
|
|
|
FILTER_ONLY
|
|
executable => sub {
|
|
|
|
my $code = $_;
|
|
my $counter = 1;
|
|
my ($success,$before,$arg,$after,$beforeblock);
|
|
|
|
while(1) {
|
|
($success,$beforeblock,$arg,$after) = PT_NEXTCOMMAND($code,"PT_BEGIN");
|
|
if ($success) {
|
|
if ($after =~ /PT_END\s*;/s) {
|
|
my $thread = $arg;
|
|
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};";
|
|
my $afterblock = $';
|
|
while (1) {
|
|
($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_YIELD_UNTIL");
|
|
if ($success) {
|
|
$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;
|
|
$counter++;
|
|
next;
|
|
}
|
|
if ($block =~ /PT_YIELD\s*;/s) {
|
|
$block = $`."\$PT_YIELD_FLAG = 0; ".$thread."->{PT_THREAD_POSITION} = 'PT_LABEL_$counter'; PT_LABEL_$counter: return PT_YIELDED unless \$PT_YIELD_FLAG;".$';
|
|
$counter++;
|
|
next;
|
|
}
|
|
($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_WAIT_UNTIL");
|
|
if ($success) {
|
|
$block=$before.$thread."->{PT_THREAD_POSITION} = 'PT_LABEL_$counter'; PT_LABEL_$counter: return PT_WAITING unless ($arg);".$after;
|
|
$counter++;
|
|
next;
|
|
}
|
|
($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_WAIT_WHILE");
|
|
if ($success) {
|
|
$block=$before.$thread."->{PT_THREAD_POSITION} = 'PT_LABEL_$counter'; PT_LABEL_$counter: return PT_WAITING if ($arg);".$after;
|
|
$counter++;
|
|
next;
|
|
}
|
|
($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_WAIT_THREAD");
|
|
if ($success) {
|
|
$block=$before."PT_WAIT_WHILE(PT_SCHEDULE(".$arg."));".$after;
|
|
next;
|
|
}
|
|
($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_SPAWN");
|
|
if ($success) {
|
|
$block=$before.$arg."->{PT_THREAD_POSITION} = 0; PT_WAIT_THREAD($arg);".$after;
|
|
next;
|
|
}
|
|
($success,$before,$arg,$after) = PT_NEXTCOMMAND($block,"PT_EXIT");
|
|
if ($success) {
|
|
$block=$before.$thread."->{PT_THREAD_POSITION} = 0; ".$thread."->{PT_THREAD_RETURN} = $arg; return PT_EXITED;".$after;
|
|
next;
|
|
}
|
|
if ($block =~ /PT_EXIT(\s*;|\s+)/s) {
|
|
$block = $`.$thread."->{PT_THREAD_POSITION} = 0; delete ".$thread."->{PT_THREAD_RETURN}; return PT_EXITED".$1.$';
|
|
next;
|
|
}
|
|
if ($block =~ /PT_RESTART(\s*;|\s)/s) {
|
|
$block = $`.$thread."->{PT_THREAD_POSITION} = 0; return PT_WAITING;".$1.$';
|
|
next;
|
|
}
|
|
last;
|
|
}
|
|
$code = $beforeblock.$block.$afterblock;
|
|
} else {
|
|
die "PT_END expected"
|
|
}
|
|
next;
|
|
}
|
|
last;
|
|
};
|
|
|
|
print $code if $DEBUG;
|
|
|
|
$_ = $code;
|
|
|
|
};
|
|
|
|
1;
|