File Coverage

blib/lib/Dallycot/Library/LOC.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Dallycot::Library::LOC;
2             our $AUTHORITY = 'cpan:JSMITH';
3              
4             # ABSTRACT: Core library of useful functions
5              
6 1     1   3130 use strict;
  1         3  
  1         34  
7 1     1   3 use warnings;
  1         1  
  1         24  
8              
9 1     1   3 use utf8;
  1         1  
  1         6  
10              
11 1     1   324 use Dallycot::Library;
  0            
  0            
12              
13             use Promises qw(deferred);
14             use List::Util qw(all any);
15             use Carp qw(croak);
16             use experimental qw(switch);
17              
18             ns 'http://www.dallycot.net/ns/loc/1.0#';
19              
20             define 'all-true' => (
21             hold => 1,
22             arity => [0],
23             options => {},
24             ), sub {
25             my ( $engine, $options, @things ) = @_;
26              
27             return $engine->TRUE unless @things;
28              
29             my $d = deferred;
30              
31             my $process_loop;
32              
33             $process_loop = sub {
34             if ( !@things ) {
35             $d->resolve( $engine->TRUE );
36             }
37             else {
38             $engine->execute( shift @things, ['Boolean'] )->done(
39             sub {
40             if ( $_[0]->value ) {
41             $process_loop->();
42             }
43             else {
44             $d->resolve( $engine->FALSE );
45             }
46             },
47             sub {
48             $d->reject(@_);
49             }
50             );
51             };
52              
53             return;
54             };
55              
56             $process_loop->();
57              
58             return $d -> promise;
59             };
60              
61             define 'any-true' => (
62             hold => 1,
63             arity => [0],
64             options => {},
65             ), sub {
66             my ( $engine, $options, @things ) = @_;
67              
68             return $engine->FALSE unless @things;
69              
70             my $d = deferred;
71              
72             my $process_loop;
73              
74             $process_loop = sub {
75             if ( !@things ) {
76             $d->resolve( $engine->TRUE );
77             }
78             else {
79             $engine->execute( shift @things, ['Boolean'] )->done(
80             sub {
81             if ( $_[0]->value ) {
82             $d -> resolve( $engine->TRUE );
83             }
84             else {
85             $process_loop -> ();
86             }
87             },
88             sub {
89             $d->reject(@_);
90             }
91             );
92             }
93              
94             return;
95             };
96              
97             $process_loop->();
98              
99             return $d -> promise;
100             };
101              
102             define 'y-combinator' => '(function) :> function(function, ___)';
103              
104             define foldl => <<'EOD';
105             (
106             folder := y-combinator(
107             (self, pad, function, stream) :> (
108             (?stream) : (
109             next := function(pad, stream');
110             [ next, self(self, next, function, stream...) ]
111             )
112             ( ) : [ ]
113             )
114             );
115             (initial, function, stream) :> (
116             (?stream) : folder(initial, function, stream)
117             ( ) : [ initial ]
118             )
119             )
120             EOD
121              
122             define foldl1 => <<'EOD';
123             (function, stream) :> (
124             (?stream) : foldl(stream', function, stream...)
125             ( ) : [ ]
126             )
127             EOD
128              
129             define map => <<'EOD';
130             y-combinator(
131             (self, mapper, stream) :> (
132             (?stream) : [ mapper(stream'), self(self, mapper, stream...) ]
133             ( ) : [ ]
134             )
135             )
136             EOD
137              
138             define filter => <<'EOD';
139             y-combinator(
140             (self, selector, stream) :> (
141             (?stream) : (
142             (selector(stream')) : [ stream', self(self, selector, stream...) ]
143             ( ) : self(self, selector, stream...)
144             )
145             ( ) : [ ]
146             )
147             )
148             EOD
149              
150             define 'build-filter' => (
151             hold => 0,
152             arity => [0],
153             options => {},
154             ), sub {
155             my ( $engine, $options, @functions ) = @_;
156              
157             my $stream = pop @functions;
158             return collect( map { maybe_promise( $_->is_lambda ) } @functions )->then(
159             sub {
160             my @flags = map {@$_} @_;
161             if ( any { !$_ } @flags ) {
162             croak "All but the last term in a filter must be lambdas.";
163             }
164             }
165             )->then(
166             sub {
167             return collect( map { maybe_promise( $_->min_arity ) } @functions )->then(
168             sub {
169             my (@arities) = map {@$_} @_;
170             if ( any { 1 != $_ } @arities ) {
171             croak "All lambdas in a filter must have arity 1.";
172             }
173             }
174             );
175             }
176             )->then(
177             sub {
178             return maybe_promise( $stream->is_lambda )->then(
179             sub {
180             my ($flag) = @_;
181             if ($flag) {
182             return $engine->make_filter( $engine->compose_filters( @functions, $stream ) );
183             }
184             else {
185             return $stream->apply_filter( $engine, $engine->compose_filters(@functions) );
186             }
187             }
188             );
189             }
190             );
191             };
192              
193             define 'build-list' => (
194             hold => 1,
195             arity => [0],
196             options => {},
197             ), sub {
198             my ( $engine, $options, @expressions ) = @_;
199              
200             given ( scalar(@expressions) ) {
201             when (0) {
202             return Dallycot::Value::EmptyStream->new;
203             }
204             when (1) {
205             return $engine->execute( $expressions[0] )->then(
206             sub {
207             my ($result) = @_;
208             Dallycot::Value::Stream->new($result);
209             }
210             );
211             }
212             default {
213             my $last_expr = pop @expressions;
214             my $promise;
215             if ( $last_expr->isa('Dallycot::Value') ) {
216             push @expressions, $last_expr;
217             }
218             else {
219             $promise = $engine->make_lambda($last_expr);
220             }
221             return $engine->collect(@expressions)->then(
222             sub {
223             my (@items) = @_;
224             my $result = Dallycot::Value::Stream->new( ( pop @items ), undef, $promise );
225             while (@items) {
226             $result = Dallycot::Value::Stream->new( ( pop @items ), $result );
227             }
228             $result;
229             }
230             );
231             }
232             }
233             };
234              
235             define 'build-map' => (
236             hold => 0,
237             arity => [0],
238             options => {}
239             ), sub {
240             my ( $engine, $options, @functions ) = @_;
241             my $stream = pop @functions;
242             return collect( map { maybe_promise( $_->is_lambda ) } @functions )->then(
243             sub {
244             my @flags = map {@$_} @_;
245             if ( any { !$_ } @flags ) {
246             croak "All but the last term in a mapping must be lambdas.";
247             }
248             }
249             )->then(
250             sub {
251             return collect( map { maybe_promise( $_->min_arity ) } @functions )->then(
252             sub {
253             my (@arities) = map {@$_} @_;
254             if ( any { 1 != $_ } @arities ) {
255             croak "All lambdas in a mapping must have arity 1.";
256             }
257             }
258             );
259             }
260             )->then(
261             sub {
262             return maybe_promise( $stream->is_lambda )->then(
263             sub {
264             my ($flag) = @_;
265              
266             if ($flag) {
267             return $engine->make_map( $engine->compose_lambdas( @functions, $stream ) );
268             }
269             else {
270             my $transform = $engine->compose_lambdas(@functions);
271              
272             return $stream->apply_map( $engine, $transform );
273             }
274             }
275             );
276             }
277             );
278             };
279              
280             define upfrom => <<'EOD';
281             y-combinator( (self, n) :> [ n, self(self, n + 1) ] )
282             EOD
283              
284             define range => <<'EOD';
285             y-combinator(
286             (self, m, n) :> (
287             (m > n) : [ m, self(self, m - 1, n) ]
288             (m = n) : [ m ]
289             (m < n) : [ m, self(self, m + 1, n) ]
290             ( ) : [ ]
291             )
292             )
293             EOD
294              
295             define 'build-set' => (
296             hold => 0,
297             arity => [0],
298             options => {}
299             ), sub {
300             my( $engine, $options, @things ) = @_;
301              
302             return Dallycot::Value::Set->new(@things);
303             };
304              
305             define 'build-vector' => (
306             hold => 0,
307             arity => [0],
308             options => {}
309             ), sub {
310             my( $engine, $options, @things ) = @_;
311              
312             return Dallycot::Value::Vector->new(@things);
313             };
314              
315             define 'compose-functions' => (
316             hold => 0,
317             arity => [0],
318             options => {}
319             ), sub {
320             my ( $engine, $options, @functions ) = @_;
321              
322             return collect( map { maybe_promise( $_->is_lambda ) } @functions )->then(
323             sub {
324             my @flags = map {@$_} @_;
325             if ( any { !$_ } @flags ) {
326             croak "All terms in a function composition must be lambdas";
327             }
328             }
329             )->then(
330             sub {
331             return collect( map { maybe_promise( $_->min_arity ) } @functions )->then(
332             sub {
333             my (@arities) = map {@$_} @_;
334              
335             if ( any { 1 != $_ } @arities ) {
336             croak "All lambdas in a function composition must have arity 1";
337             }
338             }
339             );
340             }
341             )->then(
342             sub {
343             return $engine->compose_lambdas(@functions);
344             }
345             );
346             };
347              
348             define consolidate => (
349             hold => 0,
350             arity => [1],
351             options => {},
352             ), sub {
353             my( $engine, $options, $root, @things) = @_;
354              
355             return $root unless @things;
356              
357             return $root->prepend(@things);
358             };
359              
360             sub compare (&$@) {
361             my ( $comparator, $engine, @expressions ) = @_;
362              
363             my $d = deferred;
364              
365             my $process_loop;
366              
367             $process_loop = sub {
368             my( $left_value ) = @_;
369              
370             if ( !@expressions ) {
371             $d -> resolve( $engine -> TRUE );
372             }
373             else {
374             $engine -> execute( shift @expressions ) -> then(
375             sub {
376             my ($right_value) = @_;
377             $engine->coerce( $left_value, $right_value, [ $left_value->type, $right_value->type ] )->done(
378             sub {
379             my ( $cleft, $cright ) = @_;
380             $comparator->( $cleft, $cright )->done(
381             sub {
382             if ( $_[0] ) {
383             $process_loop->( $right_value, @expressions );
384             }
385             else {
386             $d->resolve( $engine->FALSE );
387             }
388             },
389             sub {
390             $d->reject(@_);
391             }
392             );
393             },
394             sub {
395             $d -> reject(@_);
396             }
397             );
398             },
399             sub {
400             $d -> reject(@_);
401             }
402             );
403             }
404             };
405              
406             $engine->execute( shift @expressions )->done(
407             sub {
408             $process_loop->( $_[0] );
409             },
410             sub {
411             $d->reject(@_);
412             }
413             );
414              
415             return $d->promise;
416             }
417              
418             define 'all-decreasing' => (
419             hold => 1,
420             arity => [1],
421             options => {}
422             ), sub {
423             my ( $engine, $options, @things ) = @_;
424              
425             compare {
426             my($a, $b) = @_;
427             $a -> is_greater_or_equal( $engine, $b );
428             } @things;
429             };
430              
431             define 'all-increasing' => (
432             hold => 1,
433             arity => [1],
434             options => {}
435             ), sub {
436             my ( $engine, $options, @things ) = @_;
437              
438             compare {
439             my($a, $b) = @_;
440             $a -> is_less_or_equal( $engine, $b );
441             } @things;
442             };
443              
444             define 'all-strictly-decreasing' => (
445             hold => 1,
446             arity => [1],
447             options => {}
448             ), sub {
449             my ( $engine, $options, @things ) = @_;
450              
451             compare {
452             my($a, $b) = @_;
453             $a -> is_greater( $engine, $b );
454             } @things;
455             };
456              
457             define 'all-strictly-increasing' => (
458             hold => 1,
459             arity => [1],
460             options => {}
461             ), sub {
462             my ( $engine, $options, @things ) = @_;
463              
464             compare {
465             my($a, $b) = @_;
466             $a -> is_less( $engine, $b );
467             } @things;
468             };
469              
470             define 'all-equal' => (
471             hold => 1,
472             arity => [1],
473             options => {}
474             ), sub {
475             my ( $engine, $options, @things ) = @_;
476              
477             compare {
478             my($a, $b) = @_;
479             $a -> is_equal( $engine, $b );
480             } @things;
481             };
482              
483             define 'all-unique' => (
484             hold => 0,
485             arity => [1],
486             options => {}
487             ), sub {
488             my( $engine, $options, @values ) = @_;
489              
490             my @types = map { $_->type } @values;
491             return $engine->coerce( @values, \@types )->then(
492             sub {
493             my (@new_values) = @_;
494              
495             # now make sure values are all different
496             my %seen;
497             if(all { !$seen{ $_->id }++ } @new_values) {
498             return $engine->TRUE;
499             }
500             else {
501             return $engine->FALSE;
502             }
503             }
504             );
505             };
506              
507             define 'not-empty' => (
508             hold => 0,
509             arity => 1,
510             options => {}
511             ), sub {
512             my( $engine, $options, $result ) = @_;
513              
514             if ( blessed $result ) {
515             return ( $result->is_defined && !$result->is_empty ? $engine->TRUE : $engine->FALSE );
516             }
517             else {
518             return ( $engine->FALSE );
519             }
520             };
521              
522             define 'execute-list' => <<'EOD';
523             (sequence) :> (
524             last(
525             foldl(
526             (),
527             { (#2)() }/2,
528             sequence
529             )
530             )
531             )
532             EOD
533              
534             define 'invert' => (
535             hold => 0,
536             arity => 1,
537             options => {}
538             ), sub {
539             my($engine, $options, $res) = @_;
540              
541             if ( $res->isa('Dallycot::Value::Boolean') ) {
542             return Dallycot::Value::Boolean->new( !$res->value );
543             }
544             elsif ( $res->isa('Dallycot::Value::Lambda') ) {
545             return $res -> invert;
546             # return Dallycot::Value::Lambda->new(
547             # expression => Dallycot::AST::Invert->new( $res->[0] ),
548             # bindings => $res->[1],
549             # bindings_with_defaults => $res->[2],
550             # options => $res->[3],
551             # closure_environment => $res->[4],
552             # closure_namespaces => $res->[5]
553             # );
554             }
555             else {
556             return Dallycot::Value::Boolean->new( !$res->is_defined );
557             }
558             };
559              
560             1;