File Coverage

blib/lib/Dallycot/Processor.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package Dallycot::Processor;
2             our $AUTHORITY = 'cpan:JSMITH';
3              
4             # ABSTRACT: Run compiled Dallycot code.
5              
6 9     9   247558 use strict;
  9         18  
  9         341  
7 9     9   46 use warnings;
  9         13  
  9         213  
8              
9 9     9   3404 use utf8;
  9         53  
  9         41  
10 9     9   3304 use Moose;
  9         2212833  
  9         68  
11              
12 9     9   54780 use namespace::autoclean;
  9         8523  
  9         63  
13              
14 9     9   609 use Promises qw(deferred);
  9         17  
  9         61  
15              
16 9     9   5535 use experimental qw(switch);
  9         16864  
  9         108  
17              
18 9     9   5031 use Dallycot::Context;
  9         31  
  9         506  
19 9     9   3642 use Dallycot::Registry;
  9         27  
  9         441  
20 9     9   4773 use Dallycot::Resolver;
  0            
  0            
21             use Dallycot::Value;
22             use Dallycot::AST;
23              
24             use Readonly;
25              
26             use Math::BigRat try => 'GMP';
27              
28             BEGIN {
29             $Dallycot::Processor::USING_XS = eval {
30             require Dallycot::Processor::XS;
31             1;
32             };
33              
34             if ($Dallycot::Processor::USING_XS) {
35             extends 'Dallycot::Processor::XS';
36             }
37             else {
38             extends 'Dallycot::Processor::PP';
39             }
40             }
41              
42             has context => (
43             is => 'ro',
44             isa => 'Dallycot::Context',
45             handles => [
46             qw[
47             has_assignment
48             get_assignment
49             add_assignment
50             has_namespace
51             get_namespace
52             add_namespace
53             get_namespace_search_path
54             append_namespace_search_path
55             ]
56             ],
57             default => sub {
58             Dallycot::Context->new;
59             }
60             );
61              
62             has channels => (
63             is => 'ro',
64             isa => 'HashRef',
65             default => sub { +{} },
66             predicate => 'has_channels',
67             lazy => 1
68             );
69              
70             has max_cost => (
71             is => 'ro',
72             isa => 'Int',
73             default => 100_000
74             );
75              
76             has ignore_cost => (
77             is => 'ro',
78             isa => 'Bool',
79             default => 0
80             );
81              
82             has cost => (
83             is => 'ro',
84             isa => 'Int',
85             writer => '_cost',
86             default => 0
87             );
88              
89             has parent => (
90             is => 'ro',
91             predicate => 'has_parent',
92             isa => __PACKAGE__
93             );
94              
95             sub channel_send {
96             my ( $self, $channel, @items ) = @_;
97              
98             if ( $self->has_channels && exists( $self->channels->{$channel} ) ) {
99             if ( $self->channels->{$channel} ) {
100             $self->channels->{$channel}->send_data(@items);
101             }
102             }
103             elsif ( $self->has_parent ) {
104             $self->parent->channel_send( $channel, @items );
105             }
106             return;
107             }
108              
109             sub channel_read {
110             my ( $self, $channel, %options ) = @_;
111              
112             if ( $self->has_channels && exists( $self->channels->{$channel} ) ) {
113             if ( $self->channels->{$channel} ) {
114             return $self->channels->{$channel}->receive_data(%options);
115             }
116             }
117             elsif ( $self->has_parent ) {
118             return $self->parent->channel_read( $channel, %options );
119             }
120             my $d = deferred;
121             $d->resolve( Dallycot::Value::String->new('') );
122             return $d->promise;
123             }
124              
125             sub create_channel {
126             my ( $self, $channel, $object ) = @_;
127              
128             $self->channels->{$channel} = $object;
129             return;
130             }
131              
132             sub with_child_scope {
133             my ($self) = @_;
134              
135             my $ctx = $self->context;
136              
137             return __PACKAGE__->new(
138             parent => $self,
139             max_cost => $self->max_cost - $self->cost,
140             context => Dallycot::Context->new(
141             parent => $ctx,
142             namespace_search_path => [ @{ $ctx->namespace_search_path } ]
143             )
144             );
145             }
146              
147             sub with_new_closure {
148             my ( $self, $environment, $namespaces, $search_path ) = @_;
149              
150             return __PACKAGE__->new(
151             parent => $self,
152             max_cost => $self->max_cost - $self->cost,
153             context => Dallycot::Context->new(
154             environment => +{%$environment},
155             namespaces => +{%$namespaces},
156             namespace_search_path => [ @{ ( $search_path // $self->context->namespace_search_path ) } ]
157             )
158             );
159             }
160              
161             sub _execute_expr {
162             my ( $self, $expr ) = @_;
163              
164             if ( 'ARRAY' eq ref $expr ) {
165             return $self->execute(@$expr);
166             }
167             else {
168             return $self->execute($expr);
169             }
170             }
171              
172             sub collect {
173             my ( $self, @exprs ) = @_;
174              
175             return Promises::collect( map { $self->_execute_expr($_) } @exprs )->then(
176             sub {
177             map {@$_} @_;
178             }
179             );
180             }
181              
182             # for now, just returns the original values
183             sub coerce {
184             my ( $self, $a, $b, $atype, $btype ) = @_;
185              
186             my $d = deferred;
187              
188             $d->resolve( $a, $b );
189              
190             return $d->promise;
191             }
192              
193             sub make_lambda {
194             my ( $self, $expression, $bindings, $bindings_with_defaults, $options ) = @_;
195              
196             $bindings ||= [];
197             $bindings_with_defaults ||= [];
198             $options ||= {};
199              
200             return Dallycot::Value::Lambda->new(
201             expression => $expression,
202             bindings => $bindings,
203             bindings_with_defaults => $bindings_with_defaults,
204             options => $options,
205             engine => $self
206             );
207             }
208              
209             Readonly my $TRUE => Dallycot::Value::Boolean->new(1);
210             Readonly my $FALSE => Dallycot::Value::Boolean->new();
211             Readonly my $UNDEFINED => Dallycot::Value::Undefined->new;
212             Readonly my $ZERO => Dallycot::Value::Numeric->new( Math::BigRat->bzero() );
213             Readonly my $ONE => Dallycot::Value::Numeric->new( Math::BigRat->bone() );
214              
215             sub TRUE () { return $TRUE }
216             sub FALSE () { return $FALSE }
217             sub UNDEFINED () { return $UNDEFINED }
218             sub ZERO () { return $ZERO }
219             sub ONE () { return $ONE }
220              
221             sub _execute_loop {
222             my ( $self, $deferred, $expected_types, $stmt, @stmts ) = @_;
223              
224             if ( !@stmts ) {
225             $self->_execute( $expected_types, $stmt )
226             ->done( sub { $deferred->resolve(@_); }, sub { $deferred->reject(@_); } );
227             return;
228             }
229             $self->_execute( ['Any'], $stmt )
230             ->done( sub { $self->_execute_loop( $deferred, $expected_types, @stmts ) },
231             sub { $deferred->reject(@_); } );
232             return;
233             }
234              
235             sub _execute {
236             my ( $self, $expected_types, $ast ) = @_;
237              
238             my $promise = eval {
239             if ( $self->add_cost(1) > $self->max_cost ) {
240             my $d = deferred;
241             $d->reject("Exceeded maximum evaluation cost");
242             $d->promise;
243             }
244             else {
245             $ast->execute($self);
246             }
247             };
248              
249             return $promise if $promise;
250              
251             my $d = deferred;
252             if ($@) {
253             $d->reject($@);
254             }
255             else {
256             $d->reject("Unable to evaluate");
257             }
258             return $d->promise;
259             }
260              
261             sub execute {
262             my ( $self, $ast, @ast ) = @_;
263              
264             if(!defined $ast) {
265             my $d = deferred;
266             $d -> resolve(UNDEFINED);
267             return $d -> promise;
268             }
269              
270             if ( !blessed $ast) {
271             print STDERR "$ast not blessed at ", join( " ", caller ), "\n";
272             }
273              
274             my @expected_types = ('Any');
275              
276             if (@ast) {
277             my $potential_types = pop @ast;
278              
279             if ( 'ARRAY' eq ref $potential_types ) {
280             @expected_types = @$potential_types;
281             }
282             else {
283             push @ast, $potential_types;
284             }
285             }
286              
287             if (@ast) {
288             my $d = deferred;
289             $self->_execute_loop( $d, \@expected_types, $ast, @ast );
290             return $d->promise;
291             }
292             else {
293             return $self->_execute( \@expected_types, $ast );
294             }
295             }
296              
297             sub compose_lambdas {
298             my ( $self, @lambdas ) = @_;
299             @lambdas = reverse @lambdas;
300              
301             my $new_engine = $self->with_child_scope;
302              
303             my $expression = Dallycot::AST::Fetch->new('#');
304              
305             for my $idx ( 0 .. $#lambdas ) {
306             $new_engine->context->add_assignment( "__lambda_" . $idx, $lambdas[$idx] );
307             $expression
308             = Dallycot::AST::Apply->new( Dallycot::AST::Fetch->new( '__lambda_' . $idx ), [$expression] );
309             }
310              
311             return $new_engine->make_lambda( $expression, ['#'] );
312             }
313              
314             sub _add_filter_to_context {
315             my ( $engine, $idx, $filter, $expression ) = @_;
316              
317             $engine->context->add_assignment( "__lambda_" . $idx, $filter );
318             return Dallycot::AST::Apply->new( Dallycot::AST::Fetch->new( '__lambda_' . $idx ), [$expression] );
319             }
320              
321             sub compose_filters {
322             my ( $self, @filters ) = @_;
323              
324             if ( @filters == 1 ) {
325             return $filters[0];
326             }
327              
328             my $new_engine = $self->with_child_scope;
329              
330             my $expression = Dallycot::AST::Fetch->new('#');
331             my $idx = 0;
332             my @applications = map { _add_filter_to_context( $new_engine, $idx++, $_, $expression ) } @filters;
333              
334             return $new_engine->make_lambda( Dallycot::AST::All->new(@applications), ['#'] );
335             }
336              
337             sub make_map {
338             my ( $self, $transform ) = @_;
339              
340             return $self->execute(
341             Dallycot::AST::Apply->new(
342             Dallycot::Value::URI->new('http://www.dallycot.net/ns/loc/1.0#map'),
343             [ $transform, Dallycot::AST::Placeholder->new ], {}
344             )
345             );
346             }
347              
348             sub make_filter {
349             my ( $self, $selector ) = @_;
350              
351             return $self->execute(
352             Dallycot::AST::Apply->new(
353             Dallycot::Value::URI->new('http://www.dallycot.net/ns/loc/1.0#filter'),
354             [ $selector, Dallycot::AST::Placeholder->new ], {}
355             )
356             );
357             }
358              
359             __PACKAGE__->meta->make_immutable;
360              
361             require Dallycot::Library::Core;
362              
363             1;