File Coverage

blib/lib/Patro/LeumJelly.pm
Criterion Covered Total %
statement 150 208 72.1
branch 62 140 44.2
condition 10 36 27.7
subroutine 19 19 100.0
pod 0 10 0.0
total 241 413 58.3


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