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   799 use strict;
  5         37  
  5         155  
8 5     5   28 use warnings;
  5         17  
  5         171  
9 5     5   34 use PHP::Decode::Array qw(is_int_index);
  5         10  
  5         235  
10 5     5   38 use PHP::Decode::Parser qw(:all);
  5         12  
  5         5410  
11              
12             our $VERSION = '0.41';
13              
14             sub to_num {
15 11     11 0 585 my ($val) = @_;
16              
17 11 100       57 if ($val =~ /^([+-]?[0-9\.]+)/) {
18 5         42 return $1 + 0;
19             }
20 6         24 return 0;
21             }
22              
23             sub is_numval_or_null {
24 79     79 0 161 my ($s) = @_;
25              
26 79 100       286 if ($s =~ /^(\#num\d+|\#null)$/) {
27 69         224 return 1;
28             }
29 10         36 return 0;
30             }
31              
32             sub unary {
33 89     89 1 202 my ($parser, $op, $val) = @_;
34 89         144 my $result;
35 89         137 my $to_str = 0;
36              
37 89 50       234 if ($val =~ /^#const\d+$/) {
38             #printf ">> exec-op: %s %s -> skip for const\n", $op, $val;
39 0         0 return;
40             }
41 89         180 my $s = $parser->{strmap}{$val};
42              
43 89 100       207 if (is_array($val)) {
44 7         26 my $arr = $parser->{strmap}{$val};
45 7 50       20 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       27 $s = $arr->empty() ? 0 : 1;
50             }
51              
52 89 100       343 if ($op eq '++') {
    100          
    100          
    100          
    50          
    100          
    50          
53 59 100       211 if ($val =~ /^#num\d+$/) {
54 52         108 $result = int($s);
55 52         88 $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         12 $to_str = 1;
62             }
63             } elsif ($op eq '--') {
64 8 100       43 if ($val =~ /^#num\d+$/) {
65 7         12 $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       5 if ($val =~ /^#num\d+$/) {
75 1         7 $result = ~int($s);
76             } else {
77 0         0 $result = ~$s;
78 0         0 $to_str = 1;
79             }
80             } elsif ($op eq '!') {
81 15 100       41 $result = $s ? 0 : 1;
82             } elsif ($op eq 'not') {
83 0 0       0 $result = not $s ? 0 : 1;
84             } elsif ($op eq '-') {
85 5         18 $result = -int($s);
86             } elsif ($op eq '+') {
87 1         3 $result = int($s);
88             } else {
89 0         0 return;
90             }
91 89         132 my $k;
92 89 100       189 if ($to_str) {
93 8         29 $k = $parser->setstr($result);
94             } else {
95 81         203 $k = $parser->setnum($result);
96             }
97 89         349 return ($k, $result);
98             }
99              
100             sub binary {
101 251     251 1 599 my ($parser, $val1, $op, $val2) = @_;
102 251         322 my $result;
103 251         415 my $to_str = 0;
104 251         390 my $s1;
105             my $s2;
106              
107 251 100 66     998 if (($val1 =~ /^#const\d+$/) || ($val2 =~ /^#const\d+$/)) {
108             #printf ">> exec-op: %s %s %s -> skip for const\n", $val1, $op, $val2;
109 1         11 return;
110             }
111              
112 250 100       636 if ($val1 eq '#null') {
    100          
113 15 100       95 if ($val2 eq '#null') {
    100          
    50          
114 4         8 $s1 = '';
115 4         9 $s2 = '';
116             } elsif ($val2 =~ /^#num\d+$/) {
117 9         19 $s1 = 0;
118 9         28 $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         5 $s1 = '';
125 2         6 $s2 = $parser->{strmap}{$val2};
126             }
127             } elsif ($val2 eq '#null') {
128 21 100       71 if ($val1 =~ /^#num\d+$/) {
    100          
129 1         5 $s1 = $parser->{strmap}{$val1};
130 1         2 $s2 = 0;
131             } elsif (is_array($val1)) {
132 2         5 my $arr = $parser->{strmap}{$val1};
133 2 50       10 $s1 = $arr->empty() ? 0 : 1;
134 2         5 $s2 = 0;
135             } else {
136 18         53 $s1 = $parser->{strmap}{$val1};
137 18         37 $s2 = '';
138             }
139             } else {
140 214         471 $s1 = $parser->{strmap}->{$val1};
141 214         362 $s2 = $parser->{strmap}->{$val2};
142              
143             # todo: array comparisions
144             # https://www.php.net/manual/en/language.operators.comparison.php
145             #
146 214 100       448 if (is_array($val1)) {
147 2         6 my $arr = $parser->{strmap}{$val1};
148 2 50       6 $s1 = $arr->empty() ? 0 : 1;
149             }
150 214 100       481 if (is_array($val2)) {
151 4         17 my $arr = $parser->{strmap}{$val2};
152 4 50       26 $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     2450 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     96 if (is_numval_or_null($val1) && is_numval_or_null($val2)) {
179 30         71 $result = $s1 + $s2;
180             } else {
181 5     5   47 no warnings 'numeric';
  5         9  
  5         9400  
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         5 $result = $s1 * $s2;
189             } elsif ($op eq '/') {
190 1 50       4 if (int($s2) != 0) {
191 1         6 $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     53 if (is_numval($val1) && is_numval($val2)) {
    100 100        
    50 66        
    50 66        
    100 66        
    50 33        
      66        
      33        
208 8 100       29 $result = ($s1 == $s2) ? 1 : 0;
209             } elsif (is_array($val1) && is_array($val2)) {
210 2 100       8 $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       11 $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       29 $result = ($s1 eq $s2) ? 1 : 0;
221             }
222             #printf ">>>> CMP: %s == %s\n", $s1, $s2;
223 19 100       55 $result = 1 - $result if ($op eq '!=');
224             } elsif (($op eq '<') || ($op eq '>=')) {
225             # calc '<' first and then invert for '>='
226             #
227 45 100 100     113 if (is_numval($val1) && is_numval($val2)) {
    50 33        
    50 33        
    100 33        
    100 66        
    50 66        
      66        
      33        
228 39 100       101 $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       8 $result = ($s1 < to_num($s2)) ? 1 : 0;
237             } elsif (!is_numval($val1) && is_numval($val2)) {
238 3 100       9 $result = (to_num($s1) < $s2) ? 1 : 0;
239             } else {
240 0 0       0 $result = ($s1 lt $s2) ? 1 : 0;
241             }
242 45 50       107 $result = 1 - $result if ($op eq '>=');
243             } elsif (($op eq '>') || ($op eq '<=')) {
244             # calc '>' first and then invert for '<='
245             #
246 12 100 66     35 if (is_numval($val1) && is_numval($val2)) {
    50 33        
    50 33        
    50 33        
    50 33        
    0 33        
      33        
      0        
247 11 100       41 $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       10 $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       54 $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     13 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         6 $result = 0;
276             } elsif (is_numval($val1) && is_numval($val2)) {
277 1 50       9 $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     21 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       204 $s1 = 'Array' if is_array($val1);
314 110 100       227 $s2 = 'Array' if is_array($val2);
315 110         221 $result = $s1 . $s2;
316 110         176 $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         9 $result = $s1 ^ $s2;
322 3         6 $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         14 $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         5 $to_str = 1;
337             }
338             } elsif ($op eq '<<') {
339 2 50 33     25 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     3 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       9 $result = ($s1) ? $val1 : $val2;
354 2         8 return ($result, undef);
355             } elsif ($op eq '??') {
356 3 100       11 $result = ($s1) ? $val1 : $val2;
357 3         12 return ($result, undef);
358             } else {
359 0         0 return;
360             }
361 245         419 my $k;
362 245 100       444 if ($to_str) {
363 115 100       266 if ($op eq '.') {
364             # save space for memory hungry ops like repeated strconcat
365             #
366 110         356 $k = $parser->setstr_norev($result);
367             } else {
368 5         12 $k = $parser->setstr($result);
369             }
370             } else {
371 130         357 $k = $parser->setnum($result);
372             }
373 245         887 return ($k, $result);
374             }
375              
376             # check if array has just const elements
377             #
378             sub array_is_const {
379 51     51 0 671 my ($parser, $a) = @_;
380              
381 51 50       123 unless (is_array($a)) {
382 0         0 return;
383             }
384 51         137 my $arr = $parser->{strmap}{$a};
385 51         159 my $keys = $arr->get_keys();
386 51         114 foreach my $k (@$keys) {
387 98 50 33     215 unless (is_int_index($k) || is_strval($k)) {
388 0         0 return 0;
389             }
390 98         230 my $val = $arr->val($k);
391 98 50       202 if (defined $val) {
392 98 50       193 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         262 return 1;
399             }
400              
401             # compare two arrays
402             #
403             sub array_compare {
404 3     3 1 12 my ($parser, $a, $b, $check_types) = @_;
405              
406 3 50 33     9 unless (is_array($a) && is_array($b)) {
407 0         0 return;
408             }
409 3         8 my $arr_a = $parser->{strmap}{$a};
410 3         11 my $keys_a = $arr_a->get_keys();
411 3         9 my $arr_b = $parser->{strmap}{$b};
412 3         7 my $keys_b = $arr_b->get_keys();
413              
414 3         8 my $cmp = (scalar @$keys_a) - (scalar @$keys_b);
415 3 50       21 if ($cmp != 0) {
416 0         0 return $cmp;
417             }
418              
419 3         15 for (my $i=0; $i < scalar @$keys_a; $i++) {
420 3         12 my $va = $arr_a->get($keys_a->[$i]);
421 3         13 my $vb = $arr_b->get($keys_b->[$i]);
422              
423 3         19 my ($val, $result) = binary($parser, $va, '<=>', $vb);
424 3 100       36 if ($result != 0) {
425 2         11 return $result;
426             }
427 1 50       22 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         7 return 0;
435             }
436              
437             1;
438              
439             __END__