File Coverage

lib/PHP/Decode/Op.pm
Criterion Covered Total %
statement 168 226 74.3
branch 181 278 65.1
condition 80 172 46.5
subroutine 11 11 100.0
pod 3 6 50.0
total 443 693 63.9


line stmt bran cond sub pod time code
1             #
2             # PHP operators
3             # https://www.php.net/manual/en/language.operators.php
4             #
5             package PHP::Decode::Op;
6              
7 5     5   647 use strict;
  5         34  
  5         144  
8 5     5   28 use warnings;
  5         11  
  5         184  
9 5     5   34 use PHP::Decode::Array qw(is_int_index);
  5         10  
  5         245  
10 5     5   29 use PHP::Decode::Parser qw(:all);
  5         10  
  5         5181  
11              
12             our $VERSION = '0.41';
13              
14             sub to_num {
15 11     11 0 563 my ($val) = @_;
16              
17 11 100       111 if ($val =~ /^([+-]?[0-9\.]+)/) {
18 5         38 return $1 + 0;
19             }
20 6         23 return 0;
21             }
22              
23             sub is_numval_or_null {
24 79     79 0 153 my ($s) = @_;
25              
26 79 100       283 if ($s =~ /^(\#num\d+|\#null)$/) {
27 69         212 return 1;
28             }
29 10         41 return 0;
30             }
31              
32             sub unary {
33 89     89 1 205 my ($parser, $op, $val) = @_;
34 89         126 my $result;
35 89         136 my $to_str = 0;
36              
37 89 50       226 if ($val =~ /^#const\d+$/) {
38             #printf ">> exec-op: %s %s -> skip for const\n", $op, $val;
39 0         0 return;
40             }
41 89         171 my $s = $parser->{strmap}{$val};
42              
43 89 100       194 if (is_array($val)) {
44 7         23 my $arr = $parser->{strmap}{$val};
45 7 50       19 if ($op ne '!') {
46             #printf ">> exec-op: %s %s -> skip arr not allowed for op\n", $op, $val;
47 0         0 return;
48             }
49 7 100       36 $s = $arr->empty() ? 0 : 1;
50             }
51              
52 89 100       310 if ($op eq '++') {
    100          
    100          
    100          
    50          
    100          
    50          
53 59 100       232 if ($val =~ /^#num\d+$/) {
54 52         96 $result = int($s);
55 52         94 $result++;
56             } else {
57             # in php/perl ++str/str++ returns next ASCII-String
58             # https://php.net/manual/en/language.operators.increment.php
59             #
60 7         21 $result = ++$s;
61 7         14 $to_str = 1;
62             }
63             } elsif ($op eq '--') {
64 8 100       28 if ($val =~ /^#num\d+$/) {
65 7         12 $result = int($s);
66 7         14 $result--;
67             } else {
68             # in php --str/str-- returns the same ASCII-String
69             #
70 1         3 $result = $s;
71 1         3 $to_str = 1;
72             }
73             } elsif ($op eq '~') {
74 1 50       6 if ($val =~ /^#num\d+$/) {
75 1         6 $result = ~int($s);
76             } else {
77 0         0 $result = ~$s;
78 0         0 $to_str = 1;
79             }
80             } elsif ($op eq '!') {
81 15 100       78 $result = $s ? 0 : 1;
82             } elsif ($op eq 'not') {
83 0 0       0 $result = not $s ? 0 : 1;
84             } elsif ($op eq '-') {
85 5         21 $result = -int($s);
86             } elsif ($op eq '+') {
87 1         4 $result = int($s);
88             } else {
89 0         0 return;
90             }
91 89         134 my $k;
92 89 100       179 if ($to_str) {
93 8         28 $k = $parser->setstr($result);
94             } else {
95 81         220 $k = $parser->setnum($result);
96             }
97 89         310 return ($k, $result);
98             }
99              
100             sub binary {
101 251     251 1 586 my ($parser, $val1, $op, $val2) = @_;
102 251         346 my $result;
103 251         355 my $to_str = 0;
104 251         394 my $s1;
105             my $s2;
106              
107 251 100 66     936 if (($val1 =~ /^#const\d+$/) || ($val2 =~ /^#const\d+$/)) {
108             #printf ">> exec-op: %s %s %s -> skip for const\n", $val1, $op, $val2;
109 1         4 return;
110             }
111              
112 250 100       661 if ($val1 eq '#null') {
    100          
113 15 100       87 if ($val2 eq '#null') {
    100          
    50          
114 4         9 $s1 = '';
115 4         10 $s2 = '';
116             } elsif ($val2 =~ /^#num\d+$/) {
117 9         20 $s1 = 0;
118 9         26 $s2 = $parser->{strmap}{$val2};
119             } elsif (is_array($val2)) {
120 0         0 my $arr = $parser->{strmap}{$val2};
121 0         0 $s1 = 0;
122 0 0       0 $s2 = $arr->empty() ? 0 : 1;
123             } else {
124 2         8 $s1 = '';
125 2         5 $s2 = $parser->{strmap}{$val2};
126             }
127             } elsif ($val2 eq '#null') {
128 21 100       73 if ($val1 =~ /^#num\d+$/) {
    100          
129 1         4 $s1 = $parser->{strmap}{$val1};
130 1         3 $s2 = 0;
131             } elsif (is_array($val1)) {
132 2         18 my $arr = $parser->{strmap}{$val1};
133 2 50       7 $s1 = $arr->empty() ? 0 : 1;
134 2         6 $s2 = 0;
135             } else {
136 18         39 $s1 = $parser->{strmap}{$val1};
137 18         37 $s2 = '';
138             }
139             } else {
140 214         445 $s1 = $parser->{strmap}->{$val1};
141 214         381 $s2 = $parser->{strmap}->{$val2};
142              
143             # todo: array comparisions
144             # https://www.php.net/manual/en/language.operators.comparison.php
145             #
146 214 100       528 if (is_array($val1)) {
147 2         5 my $arr = $parser->{strmap}{$val1};
148 2 50       7 $s1 = $arr->empty() ? 0 : 1;
149             }
150 214 100       476 if (is_array($val2)) {
151 4         10 my $arr = $parser->{strmap}{$val2};
152 4 50       14 $s2 = $arr->empty() ? 0 : 1;
153             }
154             }
155              
156             # if arguments to bitwise ops (& | ^ ~ << >>) are
157             # strings, then the op is performend on all the chars.
158             #
159             # https://php.net/manual/en/language.operators.bitwise.php
160             #
161             # perl does also a bitwise op on a character by character
162             # bases for strings:
163             # "a" ^ " " flips case.
164             # "a" | " " sets lower case.
165             # "a" & ~" " sets upper case.
166             # "a" & " " sets to space.
167             #
168             # arithmetic operations on strings parse the numeric start
169             # of the string and convert it to number like in perl.
170             # ('2xx' -> 2, 'xxx' -> 0)
171             #
172             # Boolean operators in perl return 1 for true and the empty
173             # string for false when evaluated in string context (like printf)
174             #
175             # In string context arrays are converted to the string "Array".
176             #
177 250 100 100     2712 if ($op eq '+') {
    100 66        
    100 100        
    100 66        
    50          
    100          
    100          
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
178 35 100 100     103 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
179 30         64 $result = $s1 + $s2;
180             } else {
181 5     5   42 no warnings 'numeric';
  5         14  
  5         7773  
182             #print ">>> $s1 + $s2\n";
183 5         16 $result = $s1 + $s2;
184             }
185             } elsif ($op eq '-') {
186 1         3 $result = $s1 - $s2;
187             } elsif ($op eq '*') {
188 3         10 $result = $s1 * $s2;
189             } elsif ($op eq '/') {
190 1 50       4 if (int($s2) != 0) {
191 1         4 $result = $s1 / $s2;
192             } else {
193 0         0 $result = $s1;
194             }
195             } elsif ($op eq '%') {
196 0 0       0 if (int($s2) != 0) {
197 0         0 $result = $s1 % $s2;
198             } else {
199 0         0 $result = $s1;
200             }
201             } elsif (($op eq '==') || ($op eq '!=')) {
202             # calc '==' first and then invert for '!='
203             #
204             # for php loose/strong comparisions see:
205             # https://php.net/manual/en/types.comparisons.php#types.comparisions-loose
206             #
207 19 100 100     68 if (is_numval($val1) && is_numval($val2)) {
    100 100        
    50 66        
    50 66        
    100 66        
    50 33        
      66        
      33        
208 8 100       37 $result = ($s1 == $s2) ? 1 : 0;
209             } elsif (is_array($val1) && is_array($val2)) {
210 2 100       6 $result = (array_compare($parser, $val1, $val2) == 0) ? 1 : 0;
211             } elsif (is_array($val1) && !is_array($val2) && !is_null($val2)) {
212 0         0 $result = 0; # array is always greater
213             } elsif (!is_array($val1) && is_array($val2) && !is_null($val1)) {
214 0         0 $result = 0; # array is always greater
215             } elsif (is_numval($val1) && !is_numval($val2)) {
216 1 50       7 $result = ($s1 == to_num($s2)) ? 1 : 0;
217             } elsif (!is_numval($val1) && is_numval($val2)) {
218 0 0       0 $result = (to_num($s1) == $s2) ? 1 : 0;
219             } else {
220 8 100       35 $result = ($s1 eq $s2) ? 1 : 0;
221             }
222             #printf ">>>> CMP: %s == %s\n", $s1, $s2;
223 19 100       56 $result = 1 - $result if ($op eq '!=');
224             } elsif (($op eq '<') || ($op eq '>=')) {
225             # calc '<' first and then invert for '>='
226             #
227 45 100 100     114 if (is_numval($val1) && is_numval($val2)) {
    50 33        
    50 33        
    100 33        
    100 66        
    50 66        
      66        
      33        
228 39 100       103 $result = ($s1 < $s2) ? 1 : 0;
229             } elsif (is_array($val1) && is_array($val2)) {
230 0 0       0 $result = (array_compare($parser, $val1, $val2) < 0) ? 1 : 0;
231             } elsif (is_array($val1) && !is_array($val2) && !is_null($val2)) {
232 0         0 $result = 0; # array is always greater
233             } elsif (!is_array($val1) && is_array($val2) && !is_null($val1)) {
234 1         3 $result = 1; # array is always greater
235             } elsif (is_numval($val1) && !is_numval($val2)) {
236 2 50       6 $result = ($s1 < to_num($s2)) ? 1 : 0;
237             } elsif (!is_numval($val1) && is_numval($val2)) {
238 3 100       10 $result = (to_num($s1) < $s2) ? 1 : 0;
239             } else {
240 0 0       0 $result = ($s1 lt $s2) ? 1 : 0;
241             }
242 45 50       104 $result = 1 - $result if ($op eq '>=');
243             } elsif (($op eq '>') || ($op eq '<=')) {
244             # calc '>' first and then invert for '<='
245             #
246 12 100 66     33 if (is_numval($val1) && is_numval($val2)) {
    50 33        
    50 33        
    50 33        
    50 33        
    0 33        
      33        
      0        
247 11 100       45 $result = ($s1 > $s2) ? 1 : 0;
248             } elsif (is_array($val1) && is_array($val2)) {
249 0 0       0 $result = (array_compare($parser, $val1, $val2) > 0) ? 1 : 0;
250             } elsif (is_array($val1) && !is_array($val2) && !is_null($val2)) {
251 0         0 $result = 1; # array is always greater
252             } elsif (!is_array($val1) && is_array($val2) && !is_null($val1)) {
253 0         0 $result = 0; # array is always greater
254             } elsif (is_numval($val1) && !is_numval($val2)) {
255 1 50       4 $result = ($s1 > to_num($s2)) ? 1 : 0;
256             } elsif (!is_numval($val1) && is_numval($val2)) {
257 0 0       0 $result = (to_num($s1) > $s2) ? 1 : 0;
258             } else {
259 0 0       0 $result = ($s1 gt $s2) ? 1 : 0;
260             }
261 12 100       33 $result = 1 - $result if ($op eq '<=');
262             } elsif (($op eq '===') || ($op eq '!==')) {
263             # calc '===' first and then invert for '!=='
264             # '' and 0 and #null are different here
265 4 100 66     14 if (is_null($val1) && is_null($val2)) {
    50 66        
    50 66        
    50 66        
    100 66        
    50 33        
      66        
      33        
266 1         3 $result = 1;
267             } elsif (is_array($val1) && is_array($val2)) {
268             # also check types of array elements here
269 0 0       0 $result = (array_compare($parser, $val1, $val2, 1) == 0) ? 1 : 0;
270             } elsif (is_array($val1) && !is_array($val2) && !is_null($val2)) {
271 0         0 $result = 0; # types differ
272             } elsif (!is_array($val1) && is_array($val2) && !is_null($val1)) {
273 0         0 $result = 0; # types differ
274             } elsif (is_null($val1) || is_null($val2)) {
275 2         4 $result = 0;
276             } elsif (is_numval($val1) && is_numval($val2)) {
277 1 50       7 $result = ($s1 == $s2) ? 1 : 0;
278             } else {
279 0 0       0 $result = ($s1 eq $s2) ? 1 : 0;
280             }
281 4 50       15 $result = 1 - $result if ($op eq '!==');
282             } elsif ($op eq '<=>') {
283 6 50 33     20 if (is_null($val1) && is_null($val2)) {
    50 33        
    50 33        
    50 33        
    50 33        
    100 66        
284 0         0 $result = 0;
285             } elsif (is_array($val1) && is_array($val2)) {
286 0         0 $result = array_compare($parser, $val1, $val2);
287             } elsif (is_array($val1) && !is_array($val2)) {
288 0         0 $result = 1; # array is always greater
289             } elsif (!is_array($val1) && is_array($val2)) {
290 0         0 $result = -1; # array is always greater
291             } elsif (is_null($val1) || is_null($val2)) {
292 0 0 0     0 if (($s1 eq '') || ($s2 eq '')) {
293 0         0 $result = $s1 cmp $s2;
294             } else {
295 0         0 $result = $s1 <=> $s2;
296             }
297             } elsif (is_numval($val1) && is_numval($val2)) {
298 3         10 $result = $s1 <=> $s2;
299             } else {
300 3         9 $result = $s1 cmp $s2;
301             }
302             } elsif ($op eq '&&') {
303 0 0 0     0 $result = ($s1 && $s2) ? 1 : 0;
304             } elsif ($op eq '||') {
305 0 0 0     0 $result = ($s1 || $s2) ? 1 : 0;
306             } elsif ($op eq 'and') {
307 0 0 0     0 $result = ($s1 and $s2) ? 1 : 0;
308             } elsif ($op eq 'or') {
309 0 0 0     0 $result = ($s1 or $s2) ? 1 : 0;
310             } elsif ($op eq 'xor') {
311 0 0 0     0 $result = ($s1 xor $s2) ? 1 : 0;
312             } elsif ($op eq '.') {
313 110 50       240 $s1 = 'Array' if is_array($val1);
314 110 100       219 $s2 = 'Array' if is_array($val2);
315 110         219 $result = $s1 . $s2;
316 110         161 $to_str = 1;
317             } elsif ($op eq '^') {
318 3 50 33     8 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
319 0         0 $result = int($s1) ^ int($s2);
320             } else {
321 3         11 $result = $s1 ^ $s2;
322 3         5 $to_str = 1;
323             }
324             } elsif ($op eq '&') {
325 1 50 33     4 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
326 1         4 $result = int($s1) & int($s2);
327             } else {
328 0         0 $result = $s1 & $s2;
329 0         0 $to_str = 1;
330             }
331             } elsif ($op eq '|') {
332 2 50 33     6 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
333 0         0 $result = int($s1) | int($s2);
334             } else {
335 2         6 $result = $s1 | $s2;
336 2         3 $to_str = 1;
337             }
338             } elsif ($op eq '<<') {
339 2 50 33     7 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
340 2         8 $result = int($s1) << int($s2);
341             } else {
342 0         0 $result = $s1 << $s2;
343 0         0 $to_str = 1;
344             }
345             } elsif ($op eq '>>') {
346 1 50 33     5 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
347 1         5 $result = int($s1) >> int($s2);
348             } else {
349 0         0 $result = $s1 >> $s2;
350 0         0 $to_str = 1;
351             }
352             } elsif ($op eq '?:') {
353 2 100       8 $result = ($s1) ? $val1 : $val2;
354 2         9 return ($result, undef);
355             } elsif ($op eq '??') {
356 3 100       8 $result = ($s1) ? $val1 : $val2;
357 3         10 return ($result, undef);
358             } else {
359 0         0 return;
360             }
361 245         387 my $k;
362 245 100       468 if ($to_str) {
363 115 100       244 if ($op eq '.') {
364             # save space for memory hungry ops like repeated strconcat
365             #
366 110         395 $k = $parser->setstr_norev($result);
367             } else {
368 5         14 $k = $parser->setstr($result);
369             }
370             } else {
371 130         366 $k = $parser->setnum($result);
372             }
373 245         884 return ($k, $result);
374             }
375              
376             # check if array has just const elements
377             #
378             sub array_is_const {
379 51     51 0 665 my ($parser, $a) = @_;
380              
381 51 50       116 unless (is_array($a)) {
382 0         0 return;
383             }
384 51         112 my $arr = $parser->{strmap}{$a};
385 51         151 my $keys = $arr->get_keys();
386 51         114 foreach my $k (@$keys) {
387 98 50 33     246 unless (is_int_index($k) || is_strval($k)) {
388 0         0 return 0;
389             }
390 98         210 my $val = $arr->val($k);
391 98 50       207 if (defined $val) {
392 98 50       195 unless (is_strval($val)) {
393 0 0       0 return 0 if !is_array($val);
394 0 0       0 return 0 if !&array_is_const($parser, $val);
395             }
396             }
397             }
398 51         225 return 1;
399             }
400              
401             # compare two arrays
402             #
403             sub array_compare {
404 3     3 1 14 my ($parser, $a, $b, $check_types) = @_;
405              
406 3 50 33     8 unless (is_array($a) && is_array($b)) {
407 0         0 return;
408             }
409 3         10 my $arr_a = $parser->{strmap}{$a};
410 3         10 my $keys_a = $arr_a->get_keys();
411 3         6 my $arr_b = $parser->{strmap}{$b};
412 3         11 my $keys_b = $arr_b->get_keys();
413              
414 3         10 my $cmp = (scalar @$keys_a) - (scalar @$keys_b);
415 3 50       11 if ($cmp != 0) {
416 0         0 return $cmp;
417             }
418              
419 3         12 for (my $i=0; $i < scalar @$keys_a; $i++) {
420 3         24 my $va = $arr_a->get($keys_a->[$i]);
421 3         21 my $vb = $arr_b->get($keys_b->[$i]);
422              
423 3         27 my ($val, $result) = binary($parser, $va, '<=>', $vb);
424 3 100       11 if ($result != 0) {
425 2         10 return $result;
426             }
427 1 50       6 if ($check_types) {
428 0         0 ($val, $result) = binary($parser, $va, '===', $vb);
429 0 0       0 if ($result != 0) {
430 0         0 return $result;
431             }
432             }
433             }
434 1         6 return 0;
435             }
436              
437             1;
438              
439             __END__