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 60     60   315 use strict;
  60         96  
  60         1350  
3 60     60   259 use warnings;
  60         99  
  60         1125  
4 60     60   21485 use Data::Dumper;
  60         302389  
  60         3726  
5 60     60   375 use Carp;
  60         117  
  60         2818  
6 60     60   22411 use Storable;
  60         138817  
  60         2824  
7 60     60   15213 use MIME::Base64 ();
  60         25894  
  60         39029  
8              
9             our $VERSION = '0.13';
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 4139     4139 0 7120 my ($pkg) = @_;
21 4139         11902 return defined $proxyClasses{$pkg};
22             }
23              
24             sub handle {
25 87     87 0 179 my ($proxy) = @_;
26 87 100       260 if ($proxyClasses{ CORE::ref($proxy) }) {
27 14         42 return $proxy;
28             } else {
29 73         111 return ${$proxy};
  73         264  
30             }
31             }
32              
33             ########################################
34              
35             # bonus discovery about Storable serialization --
36             # storage order is deterministic
37              
38             sub serialize {
39 1763     1763 0 8956 return MIME::Base64::encode_base64(
40             Storable::freeze( $_[0] ), "");
41             }
42              
43             sub deserialize {
44 917 50 33 917 0 4472 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 917         5540 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 66     66 0 157 my ($objdata,$client) = @_;
61             croak "getproxy: insufficient metadata to construct proxy"
62 66 50 33     570 unless $objdata->{id} && $objdata->{ref} && $objdata->{reftype};
      33        
63 66         478 my $proxy = { %$objdata };
64 66 100       199 if ($objdata->{overload}) {
65 8         14 $proxy->{overloads} = { map {; $_ => 1 } @{$objdata->{overload}} };
  434         862  
  8         23  
66             }
67 66         208 $proxy->{client} = $client;
68 66         259 $proxy->{socket} = $client->{socket};
69 66 100       190 if ($proxy->{reftype} eq 'SCALAR') {
70 3         2000 require Patro::N2;
71 3         29 tie my $s, 'Patro::Tie::SCALAR', $proxy;
72 3         10 $proxy->{scalar} = \$s;
73 3         35 return bless $proxy, 'Patro::N2';
74             }
75              
76 63 100       166 if ($proxy->{reftype} eq 'REF') {
77 2         1801 require Patro::N6;
78 2         10 bless $proxy, 'Patro::N6';
79 2         9 return $proxy;
80             }
81            
82 61 100       139 if ($proxy->{reftype} eq 'ARRAY') {
83 27         3711 require Patro::N4;
84 27         134 tie my @a, 'Patro::Tie::ARRAY', $proxy;
85 27         64 $proxy->{array} = \@a;
86 27         134 return bless \$proxy, 'Patro::N4';
87             }
88              
89 34 100       99 if ($proxy->{reftype} eq 'HASH') {
90 22         4885 require Patro::N1;
91 22         115 tie my %h, 'Patro::Tie::HASH', $proxy;
92 22         54 $proxy->{hash} = \%h;
93 22         112 return bless \$proxy, 'Patro::N1';
94             }
95              
96 12 100 66     74 if ($proxy->{reftype} eq 'CODE' ||
97             $proxy->{reftype} eq 'CODE*') {
98 6         2933 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   76 }, @_ );
109 6         70 };
110 6         46 return bless \$proxy, 'Patro::N3';
111             }
112              
113 6 50       27 if ($proxy->{reftype} eq 'GLOB') {
114 6         3135 require Patro::N5;
115 6         36 require Symbol;
116 6         60 my $fh = Symbol::gensym();
117 6         169 tie *$fh, 'Patro::Tie::HANDLE', $proxy;
118 6         17 $proxy->{handle} = \*$fh;
119 6         26 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 433     433 0 662 my $proxy = shift;
128 433         561 my $request = shift;
129 433         1039 my ($socket,$proxy_id,$_DESTROY,$proxy_client)
130             = Patro::_fetch($proxy,qw(socket id _DESTROY client));
131 433 100       1058 if (!defined $request->{context}) {
132 15 50       49 $request->{context} = defined(wantarray) ? 1 + wantarray : 0;
133             }
134 433 50       818 if (!defined $request->{id}) {
135 0         0 $request->{id} = $proxy_id;
136             }
137              
138 433 100       809 if ($request->{has_args}) {
139             # if there are any Patro'N items in $request->{args},
140             # we should convert it to ... what?
141 330         401 foreach my $arg (@{$request->{args}}) {
  330         715  
142 459 100       817 if (isProxyRef(ref($arg))) {
143 5         9 my $id = Patro::_fetch(handle($arg),"id");
144 5         19 $arg = bless \$id, '.Patroon';
145             }
146             }
147             }
148              
149 433         801 my $sreq = serialize($request);
150 433         16766 my $resp;
151 433 50       796 if ($_DESTROY) {
152 60     60   435 no warnings 'closed';
  60         107  
  60         63888  
153 0         0 print {$socket} $sreq . "\n";
  0         0  
154 0         0 $resp = readline($socket);
155             } else {
156 433         520 print {$socket} $sreq . "\n";
  433         17815  
157 433         280337 $resp = readline($socket);
158             }
159 433 50       1914 if (!defined $resp) {
160 0         0 return serialize({context => 0, response => ""});
161             }
162 433 50       906 croak if ref($resp);
163 433         1084 $resp = deserialize_response($resp, $proxy_client);
164 433 100       840 if ($resp->{error}) {
165 2         318 croak $resp->{error};
166             }
167 431 50       768 if ($resp->{warn}) {
168 0         0 carp $resp->{warn};
169             }
170 431 50       787 if (exists $resp->{disconnect_ok}) {
171 0         0 return $resp;
172             }
173              
174             # before returning, handle side effects
175 431 100 66     879 if ($resp->{out} && ref($resp->{out}) eq 'ARRAY') {
176 12         20 for (my $i=0; $i<@{$resp->{out}}; ) {
  26         54  
177 14         31 my $index = $resp->{out}[$i++];
178 14         22 my $val = $resp->{out}[$i++];
179 14         16 eval { $_[$index] = $val };
  14         29  
180 14 50       29 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 431 100       766 if (defined $resp->{errno}) {
190             # the remote call set $!
191 11         34 $! = $resp->{errno};
192             }
193 431 100       715 if (defined $resp->{child_error}) {
194             # the remote call set $?
195 1         2 $? = $resp->{child_error};
196             }
197 431 50       682 if (defined $resp->{eval_error}) {
198             # the remote call set $@
199 0         0 $@ = $resp->{eval_error};
200             }
201              
202 431 100       719 if ($resp->{context} == 0) {
203 30         246 return;
204             }
205 401 100       655 if ($resp->{context} == 1) {
206 387         3272 return $resp->{response};
207             }
208 14 50       25 if ($resp->{context} == 2) {
209 14 50       29 if ($request->{context} == 2) {
210 14         20 return @{$resp->{response}};
  14         116  
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 433     433 0 1081 my ($response,$client) = @_;
220 433         815 $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 433 100       10212 if ($response->{context}) {
228 401 100       825 if ($response->{context} == 1) {
    50          
229             $response->{response} = depatrol($client,
230             $response->{response},
231             $response->{meta})
232 387         1184 } elsif ($response->{context} == 2) {
233             $response->{response} = [ map depatrol($client,
234             $_, $response->{meta}),
235 14         21 @{$response->{response}} ];
  14         56  
236             }
237             }
238 433 100       1137 if ($response->{out}) {
239             $response->{out} = [ map depatrol($client,$_,$response->{meta}),
240 12         16 @{$response->{out}} ];
  12         34  
241             }
242 433         842 return $response;
243             }
244              
245             sub depatrol {
246 460     460 0 1248 my ($client, $obj, $meta) = @_;
247 460 100       986 if (CORE::ref($obj) ne '.Patrobras') {
248 398         1039 return $obj;
249             }
250 62         124 my $id = $$obj;
251 62 100       204 if ($meta->{$id}) {
    50          
252 31         90 return $client->{proxies}{$id} = getproxy($meta->{$id}, $client);
253             } elsif (defined $client->{proxies}{$id}) {
254 31         99 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 30     30 0 3405 my ($ref, $y, $swap, $op) = @_;
272 30         93 my $handle = handle($ref);
273 30         136 my ($overloads,$handle_id) = Patro::_fetch($handle,"overloads","id");
274              
275 30 50 66     114 if ($overloads && $overloads->{$op}) {
276             # operation is overloaded in the remote object.
277             # ask the server to compute the operation result
278 12         97 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 18 50       91 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 6 my $obj = shift;
325 3         6 my $op = pop;
326              
327 3         10 my $handle = handle($obj);
328 3         14 my ($overloads,$handle_id) = Patro::_fetch($handle,"overloads","id");
329 3 50 33     22 if ($overloads && $overloads->{$op}) {
330             # operation is overloaded in the remote object.
331             # ask the server to compute the operation result
332 3         17 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