$self->by_id->{$self->scope->{$name}};
}
+sub bind_var_then {
+ my ($self, $var, $value, $then) = @_;
+ warn "Binding ".$var->id." to $value";
+ my $bound = $var->with_value($value);
+ $self->but(
+ by_id => { %{$self->by_id}, $var->id => $bound },
+ next_op => $then
+ );
+}
+
sub bind_stream_then {
my ($self, $var, $stream, $then) = @_;
warn "Binding ".$var->id." to $stream";
$var->bound_value; $var->clear_bound_value;
return $state->mark_choice($var) unless $var->bound_stream->is_exhausted;
}
- if (our $No_Options_Handler) {
- $No_Options_Handler->(undef);
- }
- die "Out of options";
+ $self->return_from_run(undef);
}
sub then {
$self->but(next_op => $then);
}
+sub return_from_run {
+ my (undef, $return) = @_;
+ (our $Nonlocal_Return)->($return);
+}
+
sub run {
my ($state) = @_;
with_return {
my ($return) = @_;
- local our $No_Options_Handler = $return;
+ local our $Nonlocal_Return = $return;
while (my $op = $state->next_op) {
$state = $op->run($state);
}
$self->new(%$self, @but);
}
+sub pop_return_stack {
+ my ($self) = @_;
+ my @stack = @{$self->return_stack};
+ my $top = pop @stack;
+ $self->but(return_stack => \@stack, next_op => $top);
+}
+
+sub push_return_then {
+ my ($self, $return, $then) = @_;
+ $self->but(
+ return_stack => [ @{$self->return_stack}, $return ],
+ next_op => $then
+ );
+}
+
1;
--- /dev/null
+use strictures 1;
+use Test::More;
+use aliased 'DX::Op::FromCode';
+use aliased 'DX::ArrayStream';
+use DX::ResultStream;
+use DX::Var;
+use DX::State;
+use DX::ObservationRequired;
+
+my %observe_path = (
+ '/home/me/.ssh' => { is_directory => 1 },
+ '/home/me/.ssh/authorized_keys' => { is_file => 1 },
+);
+my %paths = %observe_path;
+
+my $set_dot_ssh = FromCode->new(
+ code => sub {
+ my ($self, $state) = @_;
+ $state->bind_var_then($state->scope_var('P'), '/home/me/.ssh', $self->next);
+ }
+);
+
+my $path_status = FromCode->new(
+ code => sub {
+ my ($self, $state) = @_;
+ if (my $p = $paths{$state->scope_var('P')->bound_value}) {
+ return $state->bind_var_then($state->scope_var('PS'), $p, $self->next);
+ }
+ return $state->backtrack;
+ }
+);
+
+my $check_dir = FromCode->new(
+ code => sub {
+ my ($self, $state) = @_;
+ if ($state->scope_var('PS')->bound_value->{is_directory}) {
+ return $state->then($self->next);
+ }
+ return $state->backtrack;
+ }
+);
+
+sub make_state {
+ my ($vars, $op) = @_;
+
+ my %scope = map +($_ => $_), @{$vars};
+ my %by_id = map +($_ => DX::Var->new(id => $_)), @{$vars};
+
+ DX::State->new(
+ next_op => $op,
+ return_stack => [],
+ by_id => \%by_id,
+ scope => \%scope,
+ last_choice => []
+ );
+}
+
+my $state = make_state([ 'P', 'PS' ],
+ $set_dot_ssh->but(
+ next => $path_status->but(next => $check_dir)
+ )
+);
+
+{
+ my $res = $state->run;
+ is($res->scope_var('PS')->bound_value, $paths{'/home/me/.ssh'});
+}
+
+%paths = ();
+
+is($state->run, undef);
+
+my $pop_stack = FromCode->new(code => sub { $_[1]->pop_return_stack });
+
+my $ps_pop = $path_status->but(then => $pop_stack);
+
+my @path_status = (
+ $ps_pop,
+ FromCode->new(
+ code => sub {
+ my ($self, $state) = @_;
+ my $path = $state->scope_var('P')->bound_value;
+ $state->return_from_run(
+ DX::ObservationRequired->new(
+ observation => sub { $paths{$path} = $observe_path{$path} },
+ resume => $state->then($self->next),
+ )
+ );
+ },
+ next => $ps_pop
+ )
+);
+
+my $or_code = sub {
+ my ($self, $state) = @_;
+ my $var = DX::Var->new(id => 'OR')->with_stream(
+ ArrayStream->from_array(@path_status)
+ );
+ my $inner_or = FromCode->new(code => sub { $_[1]->then($var->bound_value) });
+
+ $state->push_return_then($self->next, $inner_or)
+ ->mark_choice($var);
+};
+
+my $or_state = make_state([ 'P', 'PS' ],
+ $set_dot_ssh->but(
+ next => FromCode->new(
+ code => $or_code,
+ next => $check_dir
+ )
+ )
+);
+
+my $ob_req = $or_state->run;
+
+$ob_req->observation->();
+
+{
+ my $res = $ob_req->resume->run;
+ is($res->scope_var('PS')->bound_value, $paths{'/home/me/.ssh'});
+}
+
+done_testing;