File Coverage

blib/lib/Patro/LeumJelly.pm
Criterion Covered Total %
statement 147 201 73.1
branch 62 136 45.5
condition 10 36 27.7
subroutine 18 18 100.0
pod 0 10 0.0
total 237 401 59.1


line stmt bran cond sub pod time code
1             package Patro::LeumJelly;
2 66     66   364 use strict;
  66         117  
  66         1579  
3 66     66   323 use warnings;
  66         140  
  66         1373  
4 66     66   24638 use Data::Dumper;
  66         354735  
  66         3937  
5 66     66   460 use Carp;
  66         114  
  66         3215  
6 66     66   26472 use Storable;
  66         167137  
  66         3513  
7 66     66   18941 use MIME::Base64 ();
  66         31131  
  66         46379  
8              
9             our $VERSION = '0.14';
10              
11             my %proxyClasses = (
12             'Patro::N1' => 0, # HASH
13             'Patro::N2' => 1, # SCALAR
14             'Patro::N3' => 0, # CODE
15             'Patro::N4' => 0, # ARRAY
16             'Patro::N5' => 0, # GLOB
17             'Patro::N6' => 1,); # REF
18              
19             sub isProxyRef {
20 4328     4328 0 7194 my ($pkg) = @_;
21 4328         13034 return defined $proxyClasses{$pkg};
22             }
23              
24             sub handle {
25 99     99 0 203 my ($proxy) = @_;
26 99 100       300 if ($proxyClasses{ CORE::ref($proxy) }) {
27 17         44 return $proxy;
28             } else {
29 82         129 return ${$proxy};
  82         332  
30             }
31             }
32              
33             ########################################
34              
35             # bonus discovery about Storable serialization --
36             # storage order is deterministic
37              
38             sub serialize {
39 1848     1848 0 8361 return MIME::Base64::encode_base64(
40             Storable::freeze( $_[0] ), "");
41             }
42              
43             sub deserialize {
44 966 50 33 966 0 4965 if ($Patro::SERVER_VERSION && $Patro::SERVER_VERSION <= 0.10) {
45             # Data::Dumper was used before v0.11
46 0         0 my $VAR1;
47 0         0 eval $_[0];
48 0         0 $VAR1;
49             } else {
50 966         5873 return Storable::thaw(
51             MIME::Base64::decode_base64($_[0]));
52             }
53             }
54              
55             ########################################
56              
57             # return a Patro::Nx object appropriate for the
58             # object metadata (containing id, ref, reftype values) and client.
59             sub getproxy {
60 69     69 0 188 my ($objdata,$client) = @_;
61             croak "getproxy: insufficient metadata to construct proxy"
62 69 50 33     649 unless $objdata->{id} && $objdata->{ref} && $objdata->{reftype};
      33        
63 69         481 my $proxy = { %$objdata };
64 69 100       236 if ($objdata->{overload}) {
65 8         10 $proxy->{overloads} = { map {; $_ => 1 } @{$objdata->{overload}} };
  434         905  
  8         27  
66             }
67 69         223 $proxy->{client} = $client;
68 69         282 $proxy->{socket} = $client->{socket};
69 69 100       200 if ($proxy->{reftype} eq 'SCALAR') {
70 3         1711 require Patro::N2;
71 3         19 tie my $s, 'Patro::Tie::SCALAR', $proxy;
72 3         7 $proxy->{scalar} = \$s;
73 3         16 return bless $proxy, 'Patro::N2';
74             }
75              
76 66 100       262 if ($proxy->{reftype} eq 'REF') {
77 2         1322 require Patro::N6;
78 2         6 bless $proxy, 'Patro::N6';
79 2         8 return $proxy;
80             }
81            
82 64 100       170 if ($proxy->{reftype} eq 'ARRAY') {
83 27         3459 require Patro::N4;
84 27         116 tie my @a, 'Patro::Tie::ARRAY', $proxy;
85 27         63 $proxy->{array} = \@a;
86 27         125 return bless \$proxy, 'Patro::N4';
87             }
88              
89 37 100       111 if ($proxy->{reftype} eq 'HASH') {
90 22         4676 require Patro::N1;
91 22         121 tie my %h, 'Patro::Tie::HASH', $proxy;
92 22         58 $proxy->{hash} = \%h;
93 22         112 return bless \$proxy, 'Patro::N1';
94             }
95              
96 15 100 66     152 if ($proxy->{reftype} eq 'CODE' ||
97             $proxy->{reftype} eq 'CODE*') {
98 6         2990 require Patro::N3;
99             $proxy->{sub} = sub {
100             return proxy_request( $proxy,
101             {
102             context => defined(wantarray) ? 1 + wantarray : 0,
103             topic => 'CODE',
104             has_args => @_ > 0,
105             args => [ @_ ],
106             command => 'invoke',
107             id => $proxy->{id}
108 3 50   3   77 }, @_ );
109 6         101 };
110 6         60 return bless \$proxy, 'Patro::N3';
111             }
112              
113 9 50       41 if ($proxy->{reftype} eq 'GLOB') {
114 9         4414 require Patro::N5;
115 9         55 require Symbol;
116 9         97 my $fh = Symbol::gensym();
117 9         269 tie *$fh, 'Patro::Tie::HANDLE', $proxy;
118 9         27 $proxy->{handle} = \*$fh;
119 9         40 return bless \$proxy, 'Patro::N5';
120             }
121              
122 0         0 croak "unsupported remote object reftype '$objdata->{reftype}'";
123             }
124              
125             # make a request through a Patro::N's client, return the response
126             sub proxy_request {
127 457     457 0 729 my $proxy = shift;
128 457         582 my $request = shift;
129 457         1128 my ($socket,$proxy_id,$_DESTROY,$proxy_client)
130             = Patro::_fetch($proxy,qw(socket id _DESTROY client));
131 457 100       1122 if (!defined $request->{context}) {
132 15 50       43 $request->{context} = defined(wantarray) ? 1 + wantarray : 0;
133             }
134 457 50       926 if (!defined $request->{id}) {
135 0         0 $request->{id} = $proxy_id;
136             }
137              
138 457 100       891 if ($request->{has_args}) {
139             # if there are any Patro'N items in $request->{args},
140             # we should convert it to ... what?
141 342         419 foreach my $arg (@{$request->{args}}) {
  342         865  
142 477 100       894 if (isProxyRef(ref($arg))) {
143 5         12 my $id = Patro::_fetch(handle($arg),"id");
144 5         38 $arg = bless \$id, '.Patroon';
145             }
146             }
147             }
148              
149 457         913 my $sreq = serialize($request);
150 457         18345 my $resp;
151 457 50       854 if ($_DESTROY) {
152 66     66   519 no warnings 'closed';
  66         147  
  66         74492  
153 0         0 print {$socket} $sreq . "\n";
  0         0  
154 0         0 $resp = readline($socket);
155             } else {
156 457         580 print {$socket} $sreq . "\n";
  457         18817  
157 457         279658 $resp = readline($socket);
158             }
159 457 50       2054 if (!defined $resp) {
160 0         0 return serialize({context => 0, response => ""});
161             }
162 457 50       984 croak if ref($resp);
163 457         1142 $resp = deserialize_response($resp, $proxy_client);
164 457 100       996 if ($resp->{error}) {
165 2         460 croak $resp->{error};
166             }
167 455 50       842 if ($resp->{warn}) {
168 0         0 carp $resp->{warn};
169             }
170 455 50       833 if (exists $resp->{disconnect_ok}) {
171 0         0 return $resp;
172             }
173              
174             # before returning, handle side effects
175 455 100 66     946 if ($resp->{out} && ref($resp->{out}) eq 'ARRAY') {
176 12         40 for (my $i=0; $i<@{$resp->{out}}; ) {
  26         62  
177 14         36 my $index = $resp->{out}[$i++];
178 14         28 my $val = $resp->{out}[$i++];
179 14         22 eval { $_[$index] = $val };
  14         31  
180 14 50       48 if ($@) {
181             next if $resp->{sideA} &&
182 0 0 0     0 $@ =~ /Modification of a read-only .../ &&
      0        
183             $_[$index] eq $val;
184 0         0 ::xdiag("failed ",[ $_[$index], $val ]);
185 0         0 croak $@;
186             }
187             }
188             }
189 455 100       819 if (defined $resp->{errno}) {
190             # the remote call set $!
191 15         33 $! = $resp->{errno};
192             }
193 455 100       899 if (defined $resp->{child_error}) {
194             # the remote call set $?
195 1         2 $? = $resp->{child_error};
196             }
197 455 50       724 if (defined $resp->{eval_error}) {
198             # the remote call set $@
199 0         0 $@ = $resp->{eval_error};
200             }
201              
202 455 100       817 if ($resp->{context} == 0) {
203 30         263 return;
204             }
205 425 100       722 if ($resp->{context} == 1) {
206 410         3358 return $resp->{response};
207             }
208 15 50       43 if ($resp->{context} == 2) {
209 15 50       39 if ($request->{context} == 2) {
210 15         21 return @{$resp->{response}};
  15         149  
211             } else {
212 0         0 return $resp->{response}[0];
213             }
214             }
215 0         0 croak "invalid response context";
216             }
217              
218             sub deserialize_response {
219 457     457 0 1125 my ($response,$client) = @_;
220 457         885 $response = deserialize($response);
221              
222             # Does the response contain SCALAR references?
223             # Does the response have meta information for these
224             # dereferenced SCALAR values?
225             # Then they must be converted to Patro::Nx objects.
226              
227 457 100       11135 if ($response->{context}) {
228 425 100       887 if ($response->{context} == 1) {
    50          
229             $response->{response} = depatrol($client,
230             $response->{response},
231             $response->{meta})
232 410         1363 } elsif ($response->{context} == 2) {
233             $response->{response} = [ map depatrol($client,
234             $_, $response->{meta}),
235 15         92 @{$response->{response}} ];
  15         105  
236             }
237             }
238 457 100       1184 if ($response->{out}) {
239             $response->{out} = [ map depatrol($client,$_,$response->{meta}),
240 12         23 @{$response->{out}} ];
  12         54  
241             }
242 457         1027 return $response;
243             }
244              
245             sub depatrol {
246 506     506 0 1438 my ($client, $obj, $meta) = @_;
247 506 100       1128 if (CORE::ref($obj) ne '.Patrobras') {
248 444         1199 return $obj;
249             }
250 62         120 my $id = $$obj;
251 62 100       215 if ($meta->{$id}) {
    50          
252 31         97 return $client->{proxies}{$id} = getproxy($meta->{$id}, $client);
253             } elsif (defined $client->{proxies}{$id}) {
254 31         107 return $client->{proxies}{$id};
255             }
256 0         0 warn "depatrol: reference $id $obj is not referred to in meta";
257 0         0 bless $obj, 'SCALAR';
258 0         0 return $obj;
259             }
260              
261             # overload handling for Patro::N1, Patro::N2, and Patro::N4. N3 and N5 too?
262              
263             my %numeric_ops = map { $_ => 1 }
264             qw# + - * / % ** << >> += -= *= /= %= **= <<= >>= <=> < <= > >= == != ^ ^=
265             & &= | |= neg ! not ~ ++ -- atan2 cos sin exp abs log sqrt int 0+ #;
266              
267             # non-numeric ops:
268             # x . x= .= cmp lt le gt ge eq ne ^. ^.= ~. "" qr -X ~~
269              
270             sub overload_handler {
271 34     34 0 3552 my ($ref, $y, $swap, $op) = @_;
272 34         133 my $handle = handle($ref);
273 34         178 my ($overloads,$handle_id) = Patro::_fetch($handle,"overloads","id");
274              
275 34 50 66     164 if ($overloads && $overloads->{$op}) {
276             # operation is overloaded in the remote object.
277             # ask the server to compute the operation result
278 12         94 return proxy_request( $handle,
279             { id => $handle_id,
280             topic => 'OVERLOAD',
281             command => $op,
282             has_args => 1,
283             args => [$y, $swap] } );
284             }
285              
286             # operation is not overloaded on the server.
287             # Do something sensible.
288 22 50       132 return 1 if $op eq 'bool';
289 0 0       0 return if $op eq '<>'; # nothing sensible to do for this op
290 0         0 my $str = overload::StrVal($ref);
291 0 0       0 if ($numeric_ops{$op}) {
292 0         0 my $num = hex($str =~ /x(\w+)/);
293 0 0       0 return $num if $op eq '0+';
294 0 0       0 return cos($num) if $op eq 'cos';
295 0 0       0 return sin($num) if $op eq 'sin';
296 0 0       0 return exp($num) if $op eq 'exp';
297 0 0       0 return log($num) if $op eq 'log';
298 0 0       0 return sqrt($num) if $op eq 'sqrt';
299 0 0       0 return int($num) if $op eq 'int';
300 0 0       0 return abs($num) if $op eq 'abs';
301 0 0       0 return -$num if $op eq 'neg';
302 0 0       0 return $num+1 if $op eq '++';
303 0 0       0 return $num-1 if $op eq '--';
304 0 0 0     0 return !$num if $op eq '!' || $op eq 'not';
305 0 0       0 return ~$num if $op eq '~';
306              
307             # binary op
308 0 0       0 ($num,$y)=($y,$num) if $swap;
309 0 0       0 return atan2($num,$y) if $op eq 'atan2';
310 0 0 0     0 return $ref if $op eq '=' || $op =~ /^[^<=>]=/;
311 0         0 return eval "$num $op \$y";
312             }
313              
314             # string operation
315 0 0       0 return $str if $op eq '""';
316 0 0 0     0 return $ref if $op eq '=' || $op =~ /^[^<=>]=/;
317 0 0       0 return qr/$str/ if $op eq 'qr';
318 0 0       0 return eval "-$y \$str" if $op eq '-X';
319 0 0       0 ($str,$y) = ($y,$str) if $swap;
320 0         0 return eval "\$str $op \$y";
321             }
322              
323             sub deref_handler {
324 3     3 0 5 my $obj = shift;
325 3         4 my $op = pop;
326              
327 3         6 my $handle = handle($obj);
328 3         9 my ($overloads,$handle_id) = Patro::_fetch($handle,"overloads","id");
329 3 50 33     14 if ($overloads && $overloads->{$op}) {
330             # operation is overloaded in the remote object.
331             # ask the server to compute the operation result
332 3         11 return proxy_request( $handle,
333             { id => $handle_id,
334             topic => 'OVERLOAD',
335             command => $op,
336             has_args => 0 } );
337             }
338 0 0         if ($op eq '@{}') { croak "Not an ARRAY reference" }
  0            
339 0 0         if ($op eq '%{}') { croak "Not a HASH reference" }
  0            
340 0 0         if ($op eq '&{}') { croak "Not a CODE reference" }
  0            
341 0           croak "Patro: invalid dereference $op";
342             }
343             1;
344              
345             =head1 NAME
346              
347             Patro::LeumJelly - functions that make Patro easier to use
348              
349             =head1 DESCRIPTION
350              
351             A collection of functions useful for the L distribution.
352             This package is for internal functions that are not of general
353             interest to the users of L.
354              
355             =head1 LICENSE AND COPYRIGHT
356              
357             MIT License
358              
359             Copyright (c) 2017, Marty O'Brien
360              
361             Permission is hereby granted, free of charge, to any person obtaining a copy
362             of this software and associated documentation files (the "Software"), to deal
363             in the Software without restriction, including without limitation the rights
364             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
365             copies of the Software, and to permit persons to whom the Software is
366             furnished to do so, subject to the following conditions:
367              
368             The above copyright notice and this permission notice shall be included in all
369             copies or substantial portions of the Software.
370              
371             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
372             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
373             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
374             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
375             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
376             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
377             SOFTWARE.
378              
379             =cut