File Coverage

blib/lib/Dallycot/Value/Lambda.pm
Criterion Covered Total %
statement 24 140 17.1
branch 0 28 0.0
condition 0 27 0.0
subroutine 8 22 36.3
pod 0 9 0.0
total 32 226 14.1


line stmt bran cond sub pod time code
1             package Dallycot::Value::Lambda;
2             our $AUTHORITY = 'cpan:JSMITH';
3              
4             # ABSTRACT: An expression with an accompanying closure environment
5              
6 23     23   14243 use strict;
  23         40  
  23         760  
7 23     23   93 use warnings;
  23         36  
  23         501  
8              
9 23     23   86 use utf8;
  23         31  
  23         131  
10 23     23   521 use parent 'Dallycot::Value::Any';
  23         31  
  23         112  
11              
12 23     23   1244 use Promises qw(deferred collect);
  23         30  
  23         117  
13              
14 23     23   6205 use Scalar::Util qw(blessed);
  23         37  
  23         1406  
15 23     23   120 use Carp qw(croak);
  23         37  
  23         1053  
16              
17 23     23   109 use Readonly;
  23         35  
  23         32362  
18              
19             Readonly my $EXPRESSION => 0;
20             Readonly my $BINDINGS => 1;
21             Readonly my $BINDINGS_WITH_DEFAULTS => 2;
22             Readonly my $OPTIONS => 3;
23             Readonly my $CLOSURE_ENVIRONMENT => 4;
24             Readonly my $CLOSURE_NAMESPACES => 5;
25             Readonly my $CLOSURE_NAMESPACE_PATH => 6;
26              
27             sub new {
28 0     0 0   my ( $class, %options ) = @_;
29              
30             my ( $expression, $bindings, $bindings_with_defaults, $options,
31             $closure_environment, $closure_namespaces, $namespace_search_path, $engine )
32             = @options{
33 0           qw(expression bindings bindings_with_defaults options closure_environment closure_namespaces namespace_search_path engine)
34             };
35              
36 0   0       $class = ref $class || $class;
37              
38 0           my ($closure_context);
39              
40 0   0       $bindings ||= [];
41 0   0       $bindings_with_defaults ||= [];
42 0   0       $options ||= {};
43 0   0       $closure_environment ||= {};
44 0   0       $closure_namespaces ||= {};
45 0   0       $namespace_search_path ||= [];
46              
47 0 0         if ($engine) {
48 0           $closure_context = $engine->context->make_closure($expression);
49 0           delete @{ $closure_context->environment }{ @$bindings, map { $_->[0] } @$bindings_with_defaults };
  0            
  0            
50 0           $closure_environment = $closure_context->environment;
51 0           $closure_namespaces = $closure_context->namespaces;
52 0           $namespace_search_path = $closure_context->namespace_search_path;
53             }
54              
55 0           return bless [
56             $expression, $bindings, $bindings_with_defaults, $options,
57             $closure_environment, $closure_namespaces, $namespace_search_path
58             ] => $class;
59             }
60              
61 0     0 0   sub is_lambda { return 1; }
62              
63             sub id {
64 0     0 0   return '^^Lambda';
65             }
66              
67             sub to_rdf {
68 0     0 0   my( $self, $parent_model ) = @_;
69              
70 0           my $model = $parent_model -> child_model(
71 0           namespace_search_path => [ @{$self -> [$CLOSURE_NAMESPACE_PATH]} ],
72             prefixes => RDF::Trine::NamespaceMap->new($self -> [$CLOSURE_NAMESPACES]),
73             );
74              
75 0           my $bnode = $model -> bnode;
76 0           $model -> add_type($bnode, 'loc:Algorithm');
77 0           $model -> add_expression($bnode, $self -> [$EXPRESSION]);
78 0           $model -> add_list(
79             $bnode, 'loc:bindings',
80 0           (map { $self -> _binding_rdf($model, $_) } @{$self->[$BINDINGS]}),
  0            
81 0           (map { $self -> _binding_rdf($model, @$_) } @{$self->[$BINDINGS_WITH_DEFAULTS]})
  0            
82             );
83 0           foreach my $opt(keys %{$self->[$OPTIONS]}) {
  0            
84 0           $model -> add_option(
85             $bnode,
86             $opt,
87             $self->[$OPTIONS]->{$opt}->to_rdf($model)
88             );
89             }
90             }
91              
92             sub as_text {
93 0     0 0   my ($self) = @_;
94 0           my ( $min, $max ) = $self->arity;
95 0 0         if ( $min < $max ) {
96 0           return "(lambda/$min..$max)";
97             }
98             else {
99 0           return "(lambda/$min)";
100             }
101             }
102              
103             sub arity {
104 0     0 0   my ($self) = @_;
105 0           my $min = scalar( @{ $self->[$BINDINGS] } );
  0            
106 0           my $more = scalar( @{ $self->[$BINDINGS_WITH_DEFAULTS] } );
  0            
107 0 0         if (wantarray) {
108 0           return ( $min, $min + $more );
109             }
110             else {
111 0           return $min + $more;
112             }
113             }
114              
115             sub min_arity {
116 0     0 0   my ($self) = @_;
117              
118 0           return scalar( @{ $self->[$BINDINGS] } );
  0            
119             }
120              
121             sub _arity_in_range {
122 0     0     my ( $self, $arity, $min, $max ) = @_;
123              
124 0 0 0       if ( $arity < $min || $arity > $max ) {
125 0 0         if ( $min == $max ) {
126 0           croak "Expected $min but found $arity arguments.";
127             }
128             else {
129 0           croak "Expected $min..$max but found $arity arguments.";
130             }
131 0           return;
132             }
133 0           return 1;
134             }
135              
136             sub _options_are_good {
137 0     0     my ( $self, $options ) = @_;
138              
139 0 0         if (%$options) {
140 0           my @bad_options = grep { not exists ${ $self->[$OPTIONS] }{$_} } keys %$options;
  0            
  0            
141 0 0         if ( @bad_options > 1 ) {
    0          
142 0           croak "Options " . join( ", ", sort(@bad_options) ) . " are not allowed.";
143             }
144             elsif (@bad_options) {
145 0           croak "Option " . $bad_options[0] . " is not allowed.";
146             }
147             }
148 0           return 1;
149             }
150              
151             sub _is_placeholder {
152 0     0     my ( $self, $obj ) = @_;
153 0   0       return blessed($obj) && $obj->isa('Dallycot::AST::Placeholder');
154             }
155              
156             sub _get_bindings {
157 0     0     my ( $self, $engine, @bindings ) = @_;
158              
159 0           my ( $min_arity, $max_arity ) = $self->arity;
160 0           my $arity = scalar(@bindings);
161              
162 0           my ( @new_bindings, @new_bindings_with_defaults, @filled_bindings, @filled_identifiers );
163              
164 0           foreach my $idx ( 0 .. $min_arity - 1 ) {
165 0 0         if ( $self->_is_placeholder( $bindings[$idx] ) ) {
166 0           push @new_bindings, $self->[$BINDINGS][$idx];
167             }
168             else {
169 0           push @filled_bindings, $bindings[$idx];
170 0           push @filled_identifiers, $self->[$BINDINGS][$idx];
171             }
172             }
173 0 0         if ( $arity > $min_arity ) {
174 0           foreach my $idx ( $min_arity .. $arity - 1 ) {
175 0 0         if ( $self->_is_placeholder( $bindings[$idx] ) ) {
176 0           push @new_bindings_with_defaults, $self->[$BINDINGS_WITH_DEFAULTS][ $idx - $min_arity ];
177             }
178             else {
179 0           push @filled_bindings, $bindings[$idx];
180 0           push @filled_identifiers, $self->[$BINDINGS_WITH_DEFAULTS][ $idx - $min_arity ]->[0];
181             }
182             }
183             }
184 0 0 0       if ( $max_arity > 0 && $arity < $max_arity ) {
185 0           foreach my $idx ( $arity .. $max_arity - 1 ) {
186 0           push @filled_bindings, $self->[$BINDINGS_WITH_DEFAULTS][ $idx - $min_arity ]->[1];
187 0           push @filled_identifiers, $self->[$BINDINGS_WITH_DEFAULTS][ $idx - $min_arity ]->[0];
188             }
189             }
190              
191 0           my %bindings;
192 0           @bindings{@filled_identifiers} = map { $engine->execute($_) } @filled_bindings;
  0            
193              
194 0           return ( \%bindings, \@new_bindings, \@new_bindings_with_defaults );
195             }
196              
197             sub _get_options {
198 0     0     my ( $self, $engine, $options ) = @_;
199              
200 0           my @option_names = keys %$options;
201              
202 0           my %ret_options;
203              
204 0           @ret_options{ keys %$options } = map { $engine->execute($_) } values %$options;
  0            
205              
206 0           return +{ %{ $self->[$OPTIONS] }, %ret_options };
  0            
207             }
208              
209 0     0 0   sub child_nodes { return () }
210              
211             sub apply {
212 0     0 0   my ( $self, $engine, $options, @bindings ) = @_;
213              
214 0           my ( $min_arity, $max_arity ) = $self->arity;
215              
216 0           my $arity = scalar(@bindings);
217              
218 0           $self->_arity_in_range( $arity, $min_arity, $max_arity );
219 0           $self->_options_are_good($options);
220 0           my ( $filled_bindings, $new_bindings, $new_bindings_with_defaults )
221             = $self->_get_bindings( $engine, @bindings );
222 0           my ($filled_options) = $self->_get_options( $engine, $options );
223              
224 0 0         my %environment = ( %{ $self->[$CLOSURE_ENVIRONMENT] || {} }, %$filled_bindings );
  0            
225              
226 0 0 0       if ( @$new_bindings || @$new_bindings_with_defaults ) {
227 0           my $promise = deferred;
228 0           $promise->resolve(
229             bless [
230             $self->[$EXPRESSION], $new_bindings, $new_bindings_with_defaults,
231             $filled_options, \%environment, $self->[$CLOSURE_NAMESPACES],
232             $self->[$CLOSURE_NAMESPACE_PATH]
233             ] => __PACKAGE__
234             );
235 0           return $promise->promise;
236             }
237             else {
238 0           my $new_engine = $engine->with_new_closure(
239 0           +{ %environment, %{$filled_options} },
240             $self->[$CLOSURE_NAMESPACES],
241             $self->[$CLOSURE_NAMESPACE_PATH]
242             );
243 0           return $new_engine->execute( $self->[$EXPRESSION] );
244             }
245             }
246              
247             1;