File Coverage

blib/lib/Data/Bind.pm
Criterion Covered Total %
statement 232 266 87.2
branch 83 110 75.4
condition 13 23 56.5
subroutine 39 49 79.5
pod 0 6 0.0
total 367 454 80.8


line stmt bran cond sub pod time code
1             package Data::Bind;
2 9     9   283524 use 5.008;
  9         35  
  9         359  
3 9     9   51 use strict;
  9         17  
  9         514  
4             our $VERSION = '0.30';
5              
6 9     9   48 use base 'Exporter';
  9         19  
  9         1200  
7             our @EXPORT = qw(bind_op bind_op2);
8 9     9   56 use base 'DynaLoader';
  9         17  
  9         696  
9             __PACKAGE__->bootstrap;
10              
11 9     9   12578 use Devel::Caller qw(caller_cv caller_args);
  9         42757  
  9         880  
12 9     9   78 use B ();
  9         15  
  9         6682  
13              
14             # XXX: Make sig take storage directly
15             sub bind_op {
16 4     4 0 7879 my %vars = @_;
17              
18 4         19 my $sig = Data::Bind->sig(map { { var => $_, is_rw => 1 } } keys %vars);
  6         52  
19 4         45 $sig->[0]->bind({ positional => [ values %vars ],
20             named => {} }, 2);
21              
22             # XXX: probably returning the var
23 4         93 return;
24             }
25              
26             sub bind_op2 {
27 5     5 0 29 my ($a, $b) = @_;
28 5 50 33     23 if (ref($a) eq 'ARRAY' && ref($b) ne 'ARRAY') {
29             # binding @array := $arrayref
30 0         0 $b = $$b;
31             }
32 5         44 _alias_a_to_b($a, $b, 0);
33             }
34              
35             sub sig {
36 35     35 0 3975 my ($class, @sigs) = @_;
37              
38 35 100       144 if (ref $sigs[0] eq 'HASH') { # one element
39 34         111 @sigs = ([@sigs]);
40             }
41              
42 35         83 return Data::Bind::SigCollection->new( [ map { $class->sig_element(@$_) } @sigs ] );
  36         144  
43             }
44              
45              
46             sub sig_element {
47 36     36 0 65 my $class = shift;
48 36         55 my $now_named = 0;
49 36         107 my ($named, $positional, $named_slurpy) = ({}, []);
50 36         56 my $invocant;
51             my $multidim;
52              
53 36         88 for my $param (@_) {
54 84 50       357 die 'more than one multidimensional slurpy argument' if $multidim;
55 84         1205 my $db_param = Data::Bind::Param->new
56             ({ container_var => $param->{var},
57             named_only => $param->{named_only},
58             is_writable => $param->{is_rw},
59             is_copy => $param->{is_copy},
60             is_slurpy => $param->{is_slurpy},
61             invocant => $param->{invocant},
62             constraint => $param->{constraint},
63             default => $param->{default},
64             p5type => substr($param->{var}, 0, 1),
65             name => substr($param->{var}, 1) });
66              
67 84         1421 $multidim = $param->{is_multidimension};
68 84 100       222 $db_param->is_slurpy(1) if $multidim;
69              
70 84 100       253 if ($param->{invocant}) {
    100          
71 1 50       9 $db_param->is_optional(1)
72             unless $param->{required};
73 1         8 $invocant = $db_param;
74             }
75             elsif ($param->{named_only}) {
76 27 100       76 if ($db_param->is_slurpy) {
77 10         48 $named_slurpy = $db_param;
78 10         21 next;
79             }
80 17         130 $now_named = 1;
81 17 100       77 $db_param->is_optional(1)
82             unless $param->{required};
83 17         127 $named->{$db_param->name} = $db_param;
84             }
85             else {
86 56 100       237 unless ($db_param->is_slurpy) {
87 34 50       348 Carp::carp("positional argument after named ones") if $now_named;
88             }
89 56 100       238 $db_param->is_optional(1)
90             if $param->{optional};
91              
92 56         86 push @{$positional}, $db_param;
  56         110  
93 56         488 $named->{$db_param->name} = $db_param;
94             }
95             }
96              
97 36         565 return Data::Bind::Sig->new
98             ({ named => $named, positional => $positional,
99             is_multidimension => $multidim,
100             invocant => $invocant,
101             named_slurpy => $named_slurpy });
102             }
103              
104             # some higher level stuff
105              
106             sub _get_cv {
107 67     67   375 my $sub = shift;
108 67         471 my $gv = B::svref_2object($sub)->GV;
109              
110 67 100       976 if ($gv->SAFENAME eq '__ANON__') {
111             # vivify a GV here
112 9     9   69 no strict 'refs';
  9         24  
  9         2139  
113 19         57 my $nonce = "__ANON__::$sub";
114 19         229 return B::svref_2object(\*$nonce)->object_2svref;
115             }
116             else {
117 48         437 return $gv->object_2svref;
118             }
119             }
120              
121             # store sig in the sig slot of the cv's gv
122             sub sub_signature {
123 26     26 0 25158 my $class = shift;
124 26         80 my $sub = shift;
125 26         68 my $cv = _get_cv($sub);
126 26         109 *$cv->{sig} = Data::Bind->sig(@_);
127 26         177 return $sub;
128             }
129              
130             sub arg_bind {
131 39     39 0 24187 my $cv = _get_cv(caller_cv(1));
132 39         72 my @install_local = @{ *$cv->{sig}->bind_all($_[1], 2) };
  39         153  
133             # We have to install the locals here, otherwise there can be
134             # side-effects when it's too many levels away.
135 33         132 for (@install_local) {
136 1         9 my ($name, $code) = @$_;
137 9     9   51 no strict 'refs';
  9         18  
  9         352  
138 9     9   45 no warnings 'redefine';
  9         16  
  9         969  
139 1         1 *{$name} = $code;
  1         6  
140 1         6 Data::Bind::_forget_unlocal(2);
141             }
142             }
143              
144             =head1 NAME
145              
146             Data::Bind - Bind and alias variables
147              
148             =head1 SYNOPSIS
149              
150             use Data::Bind;
151              
152             # bind simple variables
153             sub foo {
154             my $y = 10;
155             my $x;
156              
157             bind_op('$x' => $y);
158             }
159              
160             # bind for subroutine calls
161             Data::Bind->sub_signature
162             (\&formalize,
163             { var => '$title' },
164             { var => '&code'},
165             { var => '$subtitle', optional => 1 },
166             { var => '$case', named_only => 1 },
167             { var => '$justify', named_only => 1 });
168             sub formalize {
169             my ($title, $subtitle, $case, $justify);
170             Data::Bind->arg_bind(\@_);
171             }
172              
173             formalize([\('this is title', sub { "some code" }) ], # positional
174             { subtitle => \'hello'} ); #named
175              
176             =head1 DESCRIPTION
177              
178             This module implements the semantics for perl6-style variable binding,
179             as well as subroutine call argument passing and binding, in Perl 5.
180              
181             =head1 AUTHORS
182              
183             Chia-liang Kao <clkao@clkao.org>
184              
185             =head1 COPYRIGHT
186              
187             Copyright (c) 2006. Chia-liang Kao. All rights reserved.
188              
189             This program is free software; you can redistribute it and/or modify it
190             under the same terms as Perl itself.
191              
192             See L<http://www.perl.com/perl/misc/Artistic.html>
193              
194             =cut
195              
196             package Data::Bind::SigCollection;
197 9     9   10747 use Data::Capture;
  9         44453  
  9         564  
198              
199             sub new {
200 35     35   599 my ($class, $sigs) = @_;
201 35         185 bless $sigs, $class;
202             }
203              
204 9     9   72 use List::Util 'reduce';
  9         16  
  9         4192  
205              
206             sub arity {
207 2     2   50 my $self = shift;
208 2     0   14 reduce { $a + $b } map { $self->[$_]->arity } 0..$#{$self}
  0         0  
  2         9  
  2         12  
209             }
210              
211             sub bind_all {
212 45     45   76 my ($self, $arg, $lv) = @_;
213 45         51 my @install_local;
214 45         69 ++$lv;
215 45         60 my $i = 0;
216 45         204 my $multidim = $self->[0]->is_multidimension;
217 45         230 my @x;
218 45         129 while (@$arg) {
219 47 100 66     327 my $inv = ref($arg->[0]) && ref($arg->[0]) eq 'ARRAY' ? undef : shift @$arg;
220 47 50 66     257 last unless defined $inv || @$arg;
221 47         78 my $pos = shift @$arg;
222 47         68 my $named = shift @$arg;
223              
224 47 100       86 if ($multidim) {
225 2         16 push @x, \Data::Capture::Overload->new( { invocant => $inv, positional => $pos, named => $named });
226             }
227             else {
228 45 50       134 die 'wrong dimension' unless $self->[$i];
229 45         248 push @install_local,
230 45         62 @{ $self->[$i++]->bind({ invocant => $inv, positional => $pos, named => $named }, $lv) };
231             }
232             }
233              
234 37 100       158 if ($multidim) {
235 1         7 $self->[0]->bind( { positional => \@x }, $lv );
236             }
237              
238 37         106 return \@install_local;
239             }
240              
241              
242             sub is_compatible {
243 6     6   73 my $self = shift;
244 9     9   52 no warnings 'redefine';
  9         18  
  9         3389  
245 6     0   31 local *Data::Bind::Param::slurpy_bind = sub {};
  0         0  
246 6     10   17 local *Data::Bind::Param::bind = sub {};
  10         16  
247 6     0   17 local *Data::Bind::Array::bind = sub {};
  0         0  
248 6         8 local $@;
249 6         10 eval { $self->bind_all(\@_) };
  6         20  
250 6 100       1196 return $@ ? 0 : 1;
251             }
252              
253             sub bind {
254             # XXX: old api
255 8     8   10711 my $self = shift;
256 8 50       14 die 'old api used with multidimension sig' if $#{$self};
  8         26  
257 8   50     55 $self->[0]->bind($_[0], $_[1] || 2);
258             }
259              
260             sub prepare_binding {
261 0     0   0 shift->[0]->prepare_binding(@_);
262             }
263              
264             sub finalize_binding {
265 0     0   0 my ( $self, $binding, $lv ) = @_;
266 0   0     0 $lv ||= 1;
267 0         0 $self->[0]->finalize_binding($binding, $lv + 1);
268             }
269              
270             sub all_variable_names {
271 0 0   0   0 my %seen; grep { !$seen{$_}++ or die "duplicate variable $_ in signature" } $_[0][0]->all_variable_names;
  0         0  
  0         0  
272             }
273              
274             package Data::Bind::Sig;
275 9     9   51 use base 'Class::Accessor::Fast';
  9         13  
  9         9664  
276             __PACKAGE__->mk_accessors(qw(positional invocant named named_slurpy is_multidimension));
277 9     9   51056 use Carp qw(croak);
  9         22  
  9         506  
278 9     9   54 use Scalar::Util qw(blessed);
  9         20  
  9         454  
279 9     9   59 use PadWalker qw(peek_my);
  9         16  
  9         702  
280              
281 9     9   53 use Data::Capture;
  9         15  
  9         15431  
282              
283             sub bind {
284 60     60   5400 my ( $self, $args, $lv ) = @_;
285 60   100     137 $lv ||= 1;
286 60         276 $self->finalize_binding( $self->prepare_binding(Data::Capture->new($args)), $lv + 1 );
287             }
288              
289             sub prepare_binding {
290 60     60   507 my ($self, $capture, %opts) = @_;
291 60         98 local $Carp::CarpLevel = 2;
292              
293 60         87 my %bound;
294              
295 60         184 my $named_arg = $capture->named;
296              
297 60         227 my $bindings;
298              
299             # FIXME invocant should be ref, it's writable in perl5
300 60 100       208 if ($self->invocant) {
301 2 100       23 croak 'invocant missing'
302             if !defined $capture->invocant;
303              
304 1         6 $bindings->{$self->invocant->container_var} = [ $self->invocant, \$capture->invocant ];
305             }
306             else {
307 58 50       390 croak 'unexpected invocant'
308             if defined $capture->invocant;
309             }
310              
311 59 50       280 for my $param_name (keys %{$self->named || {}}) {
  59         167  
312 133         1050 my $param = $self->named->{$param_name};
313 133 100       838 if (my $current = delete $named_arg->{$param_name}) {
    50          
    100          
314             # XXX: handle array concating
315 29         125 $bindings->{ $param->container_var } = [ $param, $current ];
316 29         186 $bound{$param_name}++;
317             }
318             elsif ($param->default) {
319 0 0       0 $bindings->{ $param->container_var } = [ $param, $opts{no_defaults} ? undef : \$param->default->(), 'default' ];
320             }
321             elsif ($param->named_only) {
322 23 100       216 croak "named argument ".$param->name." is required"
323             unless $param->is_optional;
324             }
325             }
326              
327 54 100       564 if ($self->named_slurpy) {
328 21         97 $bindings->{ $self->named_slurpy->container_var } =
329             [ $self->named_slurpy, $named_arg, 'slurpy' ];
330             }
331             else {
332             # XXX: report extra incoming named args
333             }
334              
335 54         426 my $pos_arg = $capture->positional;
336 54 100       186 for my $param (@{$self->positional || []}) {
  54         151  
337 86 100 100     660 if ($param->is_slurpy && $param->p5type ne '$') {
338 26         339 $bindings->{ $param->container_var } = [ $param, $pos_arg, 'slurpy' ];
339 26         121 $pos_arg = [];
340 26         42 last;
341             }
342 60 100       445 next if $bound{$param->name};
343 52         299 my $current = shift @$pos_arg;
344             # XXX crap logic, simplify me
345 52 100       134 unless ($current) {
346              
347 7 50       20 if ($param->default) {
348 0 0       0 $bindings->{ $param->container_var } = [ $param, $opts{no_defaults} ? undef : \$param->default->(), "default" ];
349 0         0 next;
350             }
351              
352 7 100       45 last if $param->is_optional;
353 2         15 croak "positional argument ".$param->name." is required";
354             }
355 45         165 $bindings->{ $param->container_var } = [ $param, $current ];
356             }
357             # extra incoming positional args
358 52 100       268 if (@$pos_arg) {
359 3         48 croak "extra positional argument.";
360             }
361              
362 49         225 return $bindings;
363             }
364              
365             sub finalize_binding {
366 49     49   96 my ( $self, $bindings, $lv ) = @_;
367              
368 49   50     13903 $lv ||= 1;
369 49         469 my $pad = peek_my($lv);
370              
371 110 100       544 my @ret = map { $_->[2] ? $_->[0]->slurpy_bind($_->[1], $lv, $pad)
  49         154  
372 49         82 : $_->[0]->bind($_->[1], $lv, $pad) } values %{ $bindings };
373              
374 48         456 return \@ret;
375             }
376              
377              
378             sub arity {
379 2     2   3 my $self = shift;
380 2         3 scalar grep { !$_->is_optional } values %{$self->named};
  8         62  
  2         10  
381             }
382              
383             sub all_variable_names {
384 0     0   0 my $self = shift;
385 0         0 my %seen;
386             return (
387 0         0 grep { !$seen{$_}++ } map { $_->container_var } grep { defined } (
  0         0  
  0         0  
  0         0  
388 0         0 @{ $self->positional },
389             # FIXME also invocant
390 0         0 @{ $self->named }{ sort keys %{ $self->named } },
  0         0  
391             $self->named_slurpy,
392             )
393             );
394             }
395              
396             package Data::Bind::Param;
397 9     9   61 use base 'Class::Accessor::Fast';
  9         17  
  9         963  
398             __PACKAGE__->mk_accessors(qw(name p5type is_optional is_writable is_copy is_slurpy container_var named_only constraint default));
399 9     9   9175 use Devel::LexAlias qw(lexalias);
  9         6986  
  9         6578  
400              
401             sub slurpy_bind {
402 45     45   76 my ($self, $vars, $lv, $pad) = @_;
403 45         51 $lv++;
404              
405 45 50       109 my $ref = $pad->{$self->container_var} or Carp::confess $self->container_var;
406              
407 45 100       278 if ($self->p5type eq '@') {
408 26         123 my $i = 0;
409             # flatten
410 26         49 for my $var (@$vars) {
411 45 100       94 if (ref($var) eq 'ARRAY') {
412 2         3 Data::Bind::_av_store($ref, $i++, \$var->[$_]) for 0..$#{$var};
  2         19  
413             }
414             else {
415 43         128 Data::Bind::_av_store($ref, $i++, $var);
416             }
417             }
418 26         72 return;
419             }
420 19 50       134 if ($self->p5type eq '%') {
421             Data::Bind::_hv_store($ref, $_, $vars->{$_})
422 19         172 for keys %$vars;
423 19         55 return;
424             }
425 0         0 die "not yet";
426             }
427              
428             sub bind {
429 55     55   104 my ($self, $var, $lv, $pad) = @_;
430 55         69 $lv++;
431              
432 55 100       161 if ( my $constraint = $self->constraint ) {
433 2 50       19 unless ( $constraint->(ref $var eq 'SCALAR' ? $$var : $var, level => $lv, pad => $pad, var => $var, param => $self) ) {
    100          
434 1         9 die "Failed constraint of param " . $self->name;
435             }
436             }
437              
438 54 100       387 if ($self->p5type eq '&') {
439 1         15 return [ (caller($lv-1))[0].'::'.$self->name => $$var ];
440             }
441 53 50       383 my $ref = $pad->{$self->container_var} or Carp::confess $self->container_var;
442 53 50       351 if ($self->p5type eq '$') {
443             # XXX: check $var type etc, take additional ref
444 53 50       356 if ($self->is_copy) {
    100          
445 0         0 $$ref = $$var;
446             }
447             elsif ($self->is_writable) {
448 9         109 lexalias($lv, $self->container_var, $var);
449             }
450             else {
451 44 50 33     595 if (ref($var) eq 'ARRAY' || ref($var) eq 'HASH') {
    50          
452 0         0 Data::Bind::_alias_a_to_b($ref, \$var, 1);
453             }
454             elsif (defined $$var) {
455 44         249 Data::Bind::_alias_a_to_b($ref, $var, 1);
456             }
457             }
458 53         344 return;
459             }
460 0 0         if ($self->p5type eq '@') {
461 0           Data::Bind::_alias_a_to_b($ref, $var, !$self->is_writable);
462             }
463             else {
464 0           die 'not yet';
465             }
466 0           return;
467             }
468              
469             package Data::Bind::Array;
470 9     9   68 use base 'Tie::Array';
  9         32  
  9         10937  
471              
472             sub FETCH {
473 0     0     $_[0]->{real}->[$_[1]];
474             }
475              
476             sub STORE {
477 0     0     $_[0]->{real}->[$_[1]] = $_[2];
478             }
479              
480             sub FETCHSIZE {
481 0     0     scalar @{$_[0]->{real}};
  0            
482             }
483              
484             1;
485              
486             =head1 SEE ALSO
487              
488             L<Sub::Multi>
489              
490             B<TODO: > Add a good reference to Perl6 multiple dispatch here.
491              
492             B<TODO: > Add a good reference to Perl6 variable binding semantics
493              
494             =head1 AUTHORS
495              
496             Chia-liang Kao E<lt>clkao@clkao.orgE<gt>
497              
498             =head1 COPYRIGHT
499              
500             Copyright 2006 by Chia-liang Kao and others.
501              
502             This program is free software; you can redistribute it and/or modify it
503             under the same terms as Perl itself.
504              
505             See L<http://www.perl.com/perl/misc/Artistic.html>
506              
507             =cut