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   794 use strict;
  5         35  
  5         150  
8 5     5   32 use warnings;
  5         14  
  5         168  
9 5     5   36 use PHP::Decode::Array qw(is_int_index);
  5         20  
  5         238  
10 5     5   36 use PHP::Decode::Parser qw(:all);
  5         17  
  5         5350  
11              
12             our $VERSION = '0.41';
13              
14             sub to_num {
15 11     11 0 656 my ($val) = @_;
16              
17 11 100       64 if ($val =~ /^([+-]?[0-9\.]+)/) {
18 5         32 return $1 + 0;
19             }
20 6         30 return 0;
21             }
22              
23             sub is_numval_or_null {
24 79     79 0 170 my ($s) = @_;
25              
26 79 100       271 if ($s =~ /^(\#num\d+|\#null)$/) {
27 69         210 return 1;
28             }
29 10         32 return 0;
30             }
31              
32             sub unary {
33 89     89 1 203 my ($parser, $op, $val) = @_;
34 89         120 my $result;
35 89         132 my $to_str = 0;
36              
37 89 50       211 if ($val =~ /^#const\d+$/) {
38             #printf ">> exec-op: %s %s -> skip for const\n", $op, $val;
39 0         0 return;
40             }
41 89         177 my $s = $parser->{strmap}{$val};
42              
43 89 100       192 if (is_array($val)) {
44 7         17 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       21 $s = $arr->empty() ? 0 : 1;
50             }
51              
52 89 100       346 if ($op eq '++') {
    100          
    100          
    100          
    50          
    100          
    50          
53 59 100       195 if ($val =~ /^#num\d+$/) {
54 52         104 $result = int($s);
55 52         79 $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         16 $result = ++$s;
61 7         12 $to_str = 1;
62             }
63             } elsif ($op eq '--') {
64 8 100       31 if ($val =~ /^#num\d+$/) {
65 7         14 $result = int($s);
66 7         11 $result--;
67             } else {
68             # in php --str/str-- returns the same ASCII-String
69             #
70 1         2 $result = $s;
71 1         2 $to_str = 1;
72             }
73             } elsif ($op eq '~') {
74 1 50       6 if ($val =~ /^#num\d+$/) {
75 1         4 $result = ~int($s);
76             } else {
77 0         0 $result = ~$s;
78 0         0 $to_str = 1;
79             }
80             } elsif ($op eq '!') {
81 15 100       34 $result = $s ? 0 : 1;
82             } elsif ($op eq 'not') {
83 0 0       0 $result = not $s ? 0 : 1;
84             } elsif ($op eq '-') {
85 5         14 $result = -int($s);
86             } elsif ($op eq '+') {
87 1         8 $result = int($s);
88             } else {
89 0         0 return;
90             }
91 89         120 my $k;
92 89 100       156 if ($to_str) {
93 8         21 $k = $parser->setstr($result);
94             } else {
95 81         222 $k = $parser->setnum($result);
96             }
97 89         338 return ($k, $result);
98             }
99              
100             sub binary {
101 251     251 1 508 my ($parser, $val1, $op, $val2) = @_;
102 251         323 my $result;
103 251         334 my $to_str = 0;
104 251         390 my $s1;
105             my $s2;
106              
107 251 100 66     908 if (($val1 =~ /^#const\d+$/) || ($val2 =~ /^#const\d+$/)) {
108             #printf ">> exec-op: %s %s %s -> skip for const\n", $val1, $op, $val2;
109 1         5 return;
110             }
111              
112 250 100       617 if ($val1 eq '#null') {
    100          
113 15 100       97 if ($val2 eq '#null') {
    100          
    50          
114 4         9 $s1 = '';
115 4         9 $s2 = '';
116             } elsif ($val2 =~ /^#num\d+$/) {
117 9         26 $s1 = 0;
118 9         22 $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         6 $s1 = '';
125 2         7 $s2 = $parser->{strmap}{$val2};
126             }
127             } elsif ($val2 eq '#null') {
128 21 100       72 if ($val1 =~ /^#num\d+$/) {
    100          
129 1         4 $s1 = $parser->{strmap}{$val1};
130 1         3 $s2 = 0;
131             } elsif (is_array($val1)) {
132 2         6 my $arr = $parser->{strmap}{$val1};
133 2 50       7 $s1 = $arr->empty() ? 0 : 1;
134 2         5 $s2 = 0;
135             } else {
136 18         38 $s1 = $parser->{strmap}{$val1};
137 18         25 $s2 = '';
138             }
139             } else {
140 214         484 $s1 = $parser->{strmap}->{$val1};
141 214         410 $s2 = $parser->{strmap}->{$val2};
142              
143             # todo: array comparisions
144             # https://www.php.net/manual/en/language.operators.comparison.php
145             #
146 214 100       475 if (is_array($val1)) {
147 2         7 my $arr = $parser->{strmap}{$val1};
148 2 50       7 $s1 = $arr->empty() ? 0 : 1;
149             }
150 214 100       457 if (is_array($val2)) {
151 4         14 my $arr = $parser->{strmap}{$val2};
152 4 50       11 $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     2353 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     87 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
179 30         64 $result = $s1 + $s2;
180             } else {
181 5     5   47 no warnings 'numeric';
  5         22  
  5         7965  
182             #print ">>> $s1 + $s2\n";
183 5         17 $result = $s1 + $s2;
184             }
185             } elsif ($op eq '-') {
186 1         16 $result = $s1 - $s2;
187             } elsif ($op eq '*') {
188 3         8 $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     65 if (is_numval($val1) && is_numval($val2)) {
    100 100        
    50 66        
    50 66        
    100 66        
    50 33        
      66        
      33        
208 8 100       25 $result = ($s1 == $s2) ? 1 : 0;
209             } elsif (is_array($val1) && is_array($val2)) {
210 2 100       12 $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       10 $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       24 $result = ($s1 eq $s2) ? 1 : 0;
221             }
222             #printf ">>>> CMP: %s == %s\n", $s1, $s2;
223 19 100       60 $result = 1 - $result if ($op eq '!=');
224             } elsif (($op eq '<') || ($op eq '>=')) {
225             # calc '<' first and then invert for '>='
226             #
227 45 100 100     101 if (is_numval($val1) && is_numval($val2)) {
    50 33        
    50 33        
    100 33        
    100 66        
    50 66        
      66        
      33        
228 39 100       112 $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         2 $result = 1; # array is always greater
235             } elsif (is_numval($val1) && !is_numval($val2)) {
236 2 50       7 $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       113 $result = 1 - $result if ($op eq '>=');
243             } elsif (($op eq '>') || ($op eq '<=')) {
244             # calc '>' first and then invert for '<='
245             #
246 12 100 66     31 if (is_numval($val1) && is_numval($val2)) {
    50 33        
    50 33        
    50 33        
    50 33        
    0 33        
      33        
      0        
247 11 100       38 $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       36 $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     31 if (is_null($val1) && is_null($val2)) {
    50 66        
    50 66        
    50 66        
    100 66        
    50 33        
      66        
      33        
266 1         6 $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         7 $result = 0;
276             } elsif (is_numval($val1) && is_numval($val2)) {
277 1 50       5 $result = ($s1 == $s2) ? 1 : 0;
278             } else {
279 0 0       0 $result = ($s1 eq $s2) ? 1 : 0;
280             }
281 4 50       13 $result = 1 - $result if ($op eq '!==');
282             } elsif ($op eq '<=>') {
283 6 50 33     25 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         9 $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       215 $s1 = 'Array' if is_array($val1);
314 110 100       212 $s2 = 'Array' if is_array($val2);
315 110         216 $result = $s1 . $s2;
316 110         140 $to_str = 1;
317             } elsif ($op eq '^') {
318 3 50 33     7 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
319 0         0 $result = int($s1) ^ int($s2);
320             } else {
321 3         15 $result = $s1 ^ $s2;
322 3         4 $to_str = 1;
323             }
324             } elsif ($op eq '&') {
325 1 50 33     6 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
326 1         15 $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         5 $result = $s1 | $s2;
336 2         4 $to_str = 1;
337             }
338             } elsif ($op eq '<<') {
339 2 50 33     5 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
340 2         9 $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     3 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
347 1         4 $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       6 $result = ($s1) ? $val1 : $val2;
354 2         9 return ($result, undef);
355             } elsif ($op eq '??') {
356 3 100       9 $result = ($s1) ? $val1 : $val2;
357 3         10 return ($result, undef);
358             } else {
359 0         0 return;
360             }
361 245         345 my $k;
362 245 100       413 if ($to_str) {
363 115 100       226 if ($op eq '.') {
364             # save space for memory hungry ops like repeated strconcat
365             #
366 110         270 $k = $parser->setstr_norev($result);
367             } else {
368 5         12 $k = $parser->setstr($result);
369             }
370             } else {
371 130         364 $k = $parser->setnum($result);
372             }
373 245         836 return ($k, $result);
374             }
375              
376             # check if array has just const elements
377             #
378             sub array_is_const {
379 51     51 0 658 my ($parser, $a) = @_;
380              
381 51 50       99 unless (is_array($a)) {
382 0         0 return;
383             }
384 51         126 my $arr = $parser->{strmap}{$a};
385 51         139 my $keys = $arr->get_keys();
386 51         114 foreach my $k (@$keys) {
387 98 50 33     220 unless (is_int_index($k) || is_strval($k)) {
388 0         0 return 0;
389             }
390 98         232 my $val = $arr->val($k);
391 98 50       210 if (defined $val) {
392 98 50       185 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         202 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     7 unless (is_array($a) && is_array($b)) {
407 0         0 return;
408             }
409 3         11 my $arr_a = $parser->{strmap}{$a};
410 3         10 my $keys_a = $arr_a->get_keys();
411 3         7 my $arr_b = $parser->{strmap}{$b};
412 3         21 my $keys_b = $arr_b->get_keys();
413              
414 3         22 my $cmp = (scalar @$keys_a) - (scalar @$keys_b);
415 3 50       11 if ($cmp != 0) {
416 0         0 return $cmp;
417             }
418              
419 3         10 for (my $i=0; $i < scalar @$keys_a; $i++) {
420 3         12 my $va = $arr_a->get($keys_a->[$i]);
421 3         26 my $vb = $arr_b->get($keys_b->[$i]);
422              
423 3         15 my ($val, $result) = binary($parser, $va, '<=>', $vb);
424 3 100       15 if ($result != 0) {
425 2         12 return $result;
426             }
427 1 50       7 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__