findall
Matt S Trout [Tue, 18 Feb 2014 13:30:00 +0000 (13:30 +0000)]
lib/DX/Op/FindAll.pm [new file with mode: 0644]
lib/DX/Op/FindAllCollect.pm [new file with mode: 0644]
lib/DX/OrderedSet.pm
lib/DX/RuleSet.pm
t/findall.t [new file with mode: 0644]

diff --git a/lib/DX/Op/FindAll.pm b/lib/DX/Op/FindAll.pm
new file mode 100644 (file)
index 0000000..74caead
--- /dev/null
@@ -0,0 +1,60 @@
+package DX::Op::FindAll;
+
+use DX::Op::FromCode;
+use DX::Op::FindAllCollect;
+use DX::Var;
+use DX::OrderedSet;
+use DX::Op::Return;
+use Moo;
+
+with 'DX::Role::Op';
+
+has coll_name => (is => 'ro', required => 1);
+
+has var_name => (is => 'ro', required => 1);
+
+has body => (is => 'ro', required => 1);
+
+sub run {
+  my ($self, $state) = @_;
+  my $values = [];
+  my $coll = DX::OrderedSet->new(values => $values);
+  my $collect = DX::Op::FindAllCollect->new(
+    var_name => $self->var_name,
+    into => $values
+  );
+  my $do_body = DX::Op::FromCode->new(
+    code => sub {
+      my ($self, $state) = @_;
+      $state->push_return_then($collect, $self->next);
+    },
+    next => $self->body
+  );
+  my $var = DX::Var->new(id => "rule:findall")
+                   ->with_stream(DX::ArrayStream->from_array(
+                       $do_body, DX::Op::Return->new
+                     ));
+  my $invoke = DX::Op::FromCode->new(
+    code => sub {
+      my ($self, $state) = @_;
+      my $op = $state->resolve_value($var);
+      $state->then($op);
+    }
+  );
+  my $coll_name = $self->coll_name;
+  my $ret = DX::Op::SetScope->new(
+    scope => $state->scope,
+    next => DX::Op::FromCode->new(
+      code => sub {
+        my ($self, $state) = @_;
+        $state->bind_value($state->scope->{$coll_name} => $coll)
+              ->then($self->next);
+      },
+      next => $self->next
+    )
+  );
+  $state->assign_vars($self->var_name => {})
+        ->push_return_then($ret, $invoke)->mark_choice($var);
+}
+
+1;
diff --git a/lib/DX/Op/FindAllCollect.pm b/lib/DX/Op/FindAllCollect.pm
new file mode 100644 (file)
index 0000000..297a880
--- /dev/null
@@ -0,0 +1,18 @@
+package DX::Op::FindAllCollect;
+
+use Moo;
+
+with 'DX::Role::Op';
+
+has var_name => (is => 'ro', required => 1);
+
+has into => (is => 'ro', required => 1);
+
+sub run {
+  my ($self, $state) = @_;
+  my $current = $state->resolve_value($state->scope_var($self->var_name));
+  push @{$self->into}, $current;
+  return $state->backtrack;
+}
+
+1;
index f3a860e..06af0e8 100644 (file)
@@ -9,4 +9,8 @@ sub all { @{$_[0]->values} }
 
 sub to_stream { DX::ArrayStream->from_array($_[0]->all) }
 
+sub key_list { 0..$#{$_[0]->values} }
+
+sub get { $_[0]->values->[$_[1]] }
+
 1;
index 1559d49..1fbb924 100644 (file)
@@ -17,6 +17,7 @@ use DX::Op::Prop;
 use DX::Op::Exists;
 use DX::Op::Predicate;
 use DX::Op::HasAction;
+use DX::Op::FindAll;
 use List::Util qw(reduce);
 
 has rules => (is => 'ro', default => sub { {} });
@@ -78,6 +79,15 @@ sub _expand_op_not {
   );
 }
 
+sub _expand_op_findall {
+  my ($self, $coll_name, $var_name, @contents) = @_;
+  my $findall = DX::Op::FindAll->new(
+    coll_name => $coll_name,
+    var_name => $var_name,
+    body => $self->expand_and_link(DX::Op::Return->new, @contents),
+  );
+}
+
 sub _expand_op_member_of {
   my ($self, $member_var, $coll_var) = @_;
   DX::Op::MemberOf->new(
diff --git a/t/findall.t b/t/findall.t
new file mode 100644 (file)
index 0000000..5302bbe
--- /dev/null
@@ -0,0 +1,47 @@
+use strictures 1;
+use Test::More;
+use DX::Solver;
+use DX::OrderedSet;
+
+my $solver = DX::Solver->new;
+
+{ package My::Server;
+  use Moo;
+  has name => (is => 'ro', required => 1);
+
+  package My::ShellInstalled;
+  use Moo;
+  has server => (is => 'ro', required => 1);
+  has shell => (is => 'ro', required => 1);
+}
+
+$solver->facts->{server} = DX::OrderedSet->new(
+  values => [ map My::Server->new(name => $_), qw(one two three four five) ]
+);
+$solver->facts->{shell_installed} = DX::OrderedSet->new(
+  values => [
+    (map My::ShellInstalled->new(server => $_, shell => 'bash'),
+       qw(one three four)),
+    (map My::ShellInstalled->new(server => $_, shell => 'csh'),
+       qw(two three five)),
+  ],
+);
+
+my @r = $solver->query([ 'X' ],
+  [ findall => X => S =>
+    [ member_of => 'S', \'server' ],
+    [ exists => [ qw(Name SI) ] =>
+      [ member_of => 'SI', \'shell_installed' ],
+      [ prop => 'SI' => \'server' => 'Name' ],
+      [ prop => 'S' => \'name' => 'Name' ],
+      [ prop => 'SI' => \'shell' => \'bash' ]
+    ],
+  ]
+)->results;
+
+is_deeply(
+  [ map $_->name, $r[0]->value_for('X')->all ],
+  [ qw(one three four) ]
+);
+
+done_testing;