File Coverage

blib/lib/Dallycot/Context.pm
Criterion Covered Total %
statement 76 91 83.5
branch 18 30 60.0
condition 12 21 57.1
subroutine 17 20 85.0
pod 0 9 0.0
total 123 171 71.9


line stmt bran cond sub pod time code
1             package Dallycot::Context;
2             our $AUTHORITY = 'cpan:JSMITH';
3              
4             # ABSTRACT: Execution context with value mappings and namespaces
5              
6 10     10   311629 use strict;
  10         21  
  10         346  
7 10     10   40 use warnings;
  10         17  
  10         310  
8              
9 10     10   39 use utf8;
  10         27  
  10         59  
10 10     10   671 use Moose;
  10         371384  
  10         66  
11              
12 10     10   52680 use namespace::autoclean;
  10         1401  
  10         74  
13              
14 10     10   6024 use Array::Utils qw(unique array_minus);
  10         3140  
  10         978  
15 10     10   61 use Scalar::Util qw(blessed);
  10         11  
  10         448  
16              
17 10     10   3187 use MooseX::Types::Moose qw/ArrayRef/;
  10         310153  
  10         102  
18              
19 10     10   39650 use Carp qw(croak cluck);
  10         21  
  10         665  
20              
21 10     10   670 use experimental qw(switch);
  10         2803  
  10         74  
22              
23 10     10   1813 use Promises qw(deferred);
  10         3264  
  10         63  
24              
25             #
26             # Contexts form a chain from the kernel on down
27             # The context for a statement has no parent, but is copied from the kernel's
28             # context. Changes made are copied back to the kernel context info.
29             # Closures need to copy all of the info into a new context that is marked as
30             # a closure.
31              
32             has namespaces => ( is => 'ro', isa => 'HashRef', default => sub { +{} }, lazy => 1 );
33              
34             has environment => ( is => 'ro', isa => 'HashRef', default => sub { +{} }, lazy => 1 );
35              
36             has namespace_search_path => ( is => 'ro', isa => 'ArrayRef', default => sub { [] }, lazy => 1 );
37              
38             has parent => ( is => 'ro', isa => 'Dallycot::Context', predicate => 'has_parent' );
39              
40             has is_closure => ( is => 'ro', isa => 'Bool', default => 0 );
41              
42             sub add_namespace {
43 3     3 0 13 my ( $self, $ns, $href ) = @_;
44              
45 3 100 66     98 if ( ( $self->is_closure || $self->has_parent )
      100        
46             && defined( $self->namespaces->{$ns} ) )
47             {
48 1         199 croak "Namespaces may not be defined multiple times in a sub-context or closure";
49             }
50 2         52 $self->namespaces->{$ns} = $href;
51              
52 2         4 return;
53             }
54              
55             sub get_namespace {
56 6     6 0 424 my ( $self, $ns ) = @_;
57              
58 6 100       180 if ( exists( $self->namespaces->{$ns} ) ) {
    50          
59 5         130 return $self->namespaces->{$ns};
60             }
61             elsif ( $self->has_parent ) {
62 1         25 return $self->parent->get_namespace($ns);
63             }
64             }
65              
66             sub has_namespace {
67 0     0 0 0 my ( $self, $prefix ) = @_;
68              
69 0   0     0 return exists( $self->namespaces->{$prefix} )
70             || $self->has_parent && $self->parent->has_namespace($prefix);
71             }
72              
73             sub add_assignment {
74 3     3 0 16 my ( $self, $identifier, $expr ) = @_;
75              
76 3 100 66     92 if ( ( $self->is_closure || $self->has_parent ) ) {
77 2         47 my $d = $self->environment->{$identifier};
78 2 50 66     30 if ( $d && $d->is_resolved ) {
79 0         0 croak "Identifiers may not be redefined in a sub-context or closure";
80             }
81             }
82 2 50       7 if ( defined $expr ) {
83 2 50       17 if ( $expr->can('resolve') ) {
84 2         53 return $self->environment->{$identifier} = $expr;
85             }
86             else {
87 0         0 my $d = deferred;
88 0         0 $d->resolve($expr);
89 0         0 return $self->environment->{$identifier} = $d;
90             }
91             }
92             else {
93 0         0 return $self->environment->{$identifier} = deferred;
94             }
95             }
96              
97             sub get_assignment {
98 7     7 0 1010 my ( $self, $identifier ) = @_;
99              
100 7 100       205 if ( defined( $self->environment->{$identifier} ) ) {
    50          
101 6         139 return $self->environment->{$identifier};
102             }
103             elsif ( $self->has_parent ) {
104 1         24 return $self->parent->get_assignment($identifier);
105             }
106             }
107              
108             sub has_assignment {
109 7     7 0 691 my ( $self, $identifier ) = @_;
110              
111 7   66     196 return exists( $self->environment->{$identifier} )
112             || $self->has_parent && $self->parent->has_assignment($identifier);
113             }
114              
115             sub get_namespace_search_path {
116 0     0 0 0 my ($self) = @_;
117              
118 0         0 return $self->namespace_search_path;
119             }
120              
121             sub append_namespace_search_path {
122 0     0 0 0 my ( $self, @paths ) = @_;
123              
124 0         0 return push @{ $self->namespace_search_path }, @paths;
  0         0  
125             }
126              
127             sub make_closure {
128 1     1 0 3 my ( $self, $node ) = @_;
129              
130 1         3 my ( %namespaces, %environment );
131              
132             # we only copy the values we can use
133 1         4 my @stack = ($node);
134 1         2 my @identifiers = ();
135              
136 1         4 while (@stack) {
137 1         3 $node = shift @stack;
138 1 50       4 if ( !ref $node ) {
139 0         0 cluck "We have a non-ref node! ($node)";
140             }
141              
142 1         12 push @stack, $node->child_nodes;
143              
144 1         4 my @ids = $node->identifiers;
145 1 50       5 if (@ids) {
146 1         7 my @new_ids = array_minus( @ids, @identifiers );
147              
148             #push @stack, grep { ref } map { $self->get_assignment($_) } @new_ids;
149 1         11 push @identifiers, @new_ids;
150             }
151             }
152              
153 1         2 @identifiers = values %{ +{ map { $_ => $_ } @identifiers } };
  1         3  
  1         8  
154              
155 1         3 for my $identifier (@identifiers) {
156 1 50 33     7 if ( is_ArrayRef($identifier) ) {
    50          
157 0 0       0 if ( !defined( $namespaces{ $identifier->[0] } ) ) {
158 0         0 $namespaces{ $identifier->[0] } = $self->get_namespace( $identifier->[0] );
159             }
160             }
161             elsif ( substr( $identifier, 0, 1 ) ne '#' && !defined( $environment{$identifier} ) ) {
162 1         344 my $value = $self->get_assignment($identifier);
163 1 50       10 $environment{$identifier} = $value if blessed $value;
164             }
165             }
166              
167             # making the closure a child/parent allows setting overrides once in the closure code
168 1         30 return $self->new(
169             namespaces => \%namespaces,
170             environment => \%environment,
171 1         4 namespace_search_path => [@{$self->namespace_search_path}]
172             );
173             }
174              
175             __PACKAGE__->meta->make_immutable;
176              
177             1;