File Coverage

blib/lib/JQ/Lite/Filters.pm
Criterion Covered Total %
statement 1332 1385 96.1
branch 676 806 83.8
condition 145 220 65.9
subroutine 7 7 100.0
pod 0 1 0.0
total 2160 2419 89.2


line stmt bran cond sub pod time code
1             package JQ::Lite::Filters;
2              
3 176     176   2451 use strict;
  176         3494  
  176         8238  
4 176     176   3705 use warnings;
  176         1713  
  176         13283  
5              
6 176     176   4232 use List::Util qw(sum min max);
  176         1901  
  176         17098  
7 176     176   2831 use Scalar::Util qw(looks_like_number);
  176         292  
  176         12360  
8 176     176   956 use B qw(SVp_IOK SVp_NOK);
  176         4807  
  176         21953  
9 176     176   63606 use JQ::Lite::Util ();
  176         704  
  176         3828607  
10              
11             sub apply {
12 1390     1390 0 22143 my ($self, $part, $results_ref, $out_ref) = @_;
13              
14 1390         2196 my @results = @$results_ref;
15 1390         1595 my @next_results;
16              
17 1390         23968 my $normalized = JQ::Lite::Util::_strip_wrapping_parens($part);
18              
19 1390         2794 my @sequence_parts = JQ::Lite::Util::_split_top_level_commas($normalized);
20 1390 100       2713 if (@sequence_parts > 1) {
21 12         17 @next_results = ();
22              
23 12         14 for my $item (@results) {
24 14         32 my $json = JQ::Lite::Util::_encode_json($item);
25              
26 14         1133 for my $segment (@sequence_parts) {
27 30 50       72 next unless defined $segment;
28 30         40 my $filter = $segment;
29 30         127 $filter =~ s/^\s+|\s+$//g;
30 30         52 $filter = JQ::Lite::Util::_strip_wrapping_parens($filter);
31 30 50       56 next if $filter eq '';
32              
33 30 100       64 if ($filter !~ /^\s*[\[{(]/) {
34 25         46 my ($values, $ok) = JQ::Lite::Util::_evaluate_value_expression($self, $item, $filter);
35 25 100       46 if ($ok) {
36 23 100       39 if (@$values) {
37 22         31 push @next_results, @$values;
38 22         69 next;
39             }
40             # fall through to the full evaluator when the shortcut produced no
41             # values so we don't accidentally drop a legitimate branch
42             }
43             }
44              
45 8         25 my @outputs = $self->run_query($json, $filter);
46 8         18 push @next_results, @outputs;
47             }
48             }
49              
50 12         23 @$out_ref = @next_results;
51 12         39 return 1;
52             }
53              
54             # support for variable references like $var or $var.path
55 1378 100       3093 if ($normalized =~ /^\$(\w+)(.*)$/s) {
56 15   50     90 my ($var_name, $suffix) = ($1, $2 // '');
57 15         30 for my $item (@results) {
58 16         47 my @values = JQ::Lite::Util::_evaluate_variable_reference($self, $var_name, $suffix);
59 16 100       49 if (@values) {
60 15         37 push @next_results, @values;
61             } else {
62 1         4 push @next_results, undef;
63             }
64             }
65 15         43 @$out_ref = @next_results;
66 15         65 return 1;
67             }
68              
69             # support for binding the current value to a variable: . as $x | ...
70 1363 100       2493 if ($normalized =~ /^as\s+\$(\w+)$/) {
71 2         38 my $var_name = $1;
72 2         4 @next_results = ();
73              
74 2         4 for my $item (@results) {
75 2         7 $self->{_vars}{$var_name} = $item;
76 2         3 push @next_results, $item;
77             }
78              
79 2         4 @$out_ref = @next_results;
80 2         7 return 1;
81             }
82              
83 1361 100       2587 if ($normalized =~ /^try\b/) {
84 14         16 my $body = $normalized;
85 14         44 $body =~ s/^try\s*//;
86              
87 14         32 my ($try_expr, $catch_expr) = ($body, '');
88              
89 14         30 my %pairs = (
90             '(' => ')',
91             '[' => ']',
92             '{' => '}',
93             );
94 14         27 my %closing = reverse %pairs;
95              
96 14         15 my @stack;
97             my $string;
98 14         16 my $escape = 0;
99 14         15 my $catch_index = undef;
100 14         15 my $nested_try_depth = 0;
101 14         28 for (my $i = 0; $i < length $body; $i++) {
102 191         179 my $ch = substr($body, $i, 1);
103              
104 191 100       208 if (defined $string) {
105 18 50       22 if ($escape) {
106 0         0 $escape = 0;
107 0         0 next;
108             }
109              
110 18 50       19 if ($ch eq '\\') {
111 0         0 $escape = 1;
112 0         0 next;
113             }
114              
115 18 100       19 if ($ch eq $string) {
116 2         2 undef $string;
117             }
118              
119 18         22 next;
120             }
121              
122 173 100 66     334 if ($ch eq "'" || $ch eq '"') {
123 2         2 $string = $ch;
124 2         3 next;
125             }
126              
127 171 100       206 if (exists $pairs{$ch}) {
128 9         12 push @stack, $ch;
129 9         16 next;
130             }
131              
132 162 100       209 if (exists $closing{$ch}) {
133 9 50       16 last unless @stack;
134 9         11 my $open = pop @stack;
135 9 50       18 last unless $pairs{$open} eq $ch;
136 9         14 next;
137             }
138              
139 153 100       195 next if @stack;
140              
141 77 100       103 if (substr($body, $i) =~ /^try\b/) {
142 1 50       3 if ($i > 0) {
143 0         0 my $prev = substr($body, $i - 1, 1);
144 0 0       0 next unless $prev =~ /[\s\(\[\{\|,]/;
145             }
146 1         2 $nested_try_depth++;
147 1         2 next;
148             }
149              
150 76 100       121 if (substr($body, $i) =~ /^catch\b/) {
151 12 50       20 if ($i > 0) {
152 12         20 my $prev = substr($body, $i - 1, 1);
153 12 100       32 next unless $prev =~ /[\s\)\]\}\|,]/;
154             }
155              
156 11 100       16 if ($nested_try_depth > 0) {
157 1         2 $nested_try_depth--;
158 1         2 next;
159             }
160              
161 10         10 $catch_index = $i;
162 10         13 last;
163             }
164             }
165              
166 14 100       18 if (defined $catch_index) {
167 10         12 $try_expr = substr($body, 0, $catch_index);
168 10         15 $catch_expr = substr($body, $catch_index + 5);
169             }
170              
171 14   50     42 $try_expr = JQ::Lite::Util::_strip_wrapping_parens($try_expr // '');
172 14   50     34 $catch_expr = JQ::Lite::Util::_strip_wrapping_parens($catch_expr // '');
173 14 50       32 $catch_expr =~ s/^\s+// if defined $catch_expr;
174              
175 14         21 @next_results = ();
176              
177 14         18 VALUE: for my $value (@results) {
178 14         24 my $json = JQ::Lite::Util::_encode_json($value);
179 14         1182 my @outputs;
180             my $error;
181              
182             {
183 14         15 local $@;
  14         15  
184 14 100       19 eval { @outputs = $self->run_query($json, $try_expr); 1 } or $error = $@;
  14         35  
  7         13  
185             }
186              
187 14 100       23 if (!$error) {
188 7         12 push @next_results, @outputs;
189 7         14 next VALUE;
190             }
191              
192 7         10 my $message = $error;
193 7 50       48 $message =~ s/\s+$// if defined $message;
194              
195 7 100 66     28 if (defined $catch_expr && length $catch_expr) {
196 6 50       7 my %existing = %{ $self->{_vars} || {} };
  6         24  
197 6         22 local $self->{_vars} = { %existing, error => $message };
198              
199 6         13 my ($catch_values, $catch_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $value, $catch_expr);
200 6 100       14 if ($catch_ok) {
201 4 50       10 push @next_results, @$catch_values ? @$catch_values : (undef);
202             }
203             else {
204 2         3 my @catch_outputs;
205             my $catch_error;
206              
207             {
208 2         3 local $@;
  2         2  
209 2 50       3 eval { @catch_outputs = $self->run_query($json, $catch_expr); 1 } or $catch_error = $@;
  2         7  
  2         7  
210             }
211              
212 2 50       6 if ($catch_error) {
213 0         0 push @next_results, undef;
214             }
215             else {
216 2         4 push @next_results, @catch_outputs;
217             }
218             }
219              
220 6         22 next VALUE;
221             }
222              
223 1         2 push @next_results, undef;
224             }
225              
226 14         23 @$out_ref = @next_results;
227 14         61 return 1;
228             }
229              
230 1347 100       3115 if (JQ::Lite::Util::_looks_like_expression($normalized)) {
231 59         77 my @evaluated;
232 59         75 my $all_ok = 1;
233              
234 59         87 for my $item (@results) {
235 58         169 my ($values, $ok) = JQ::Lite::Util::_evaluate_value_expression($self, $item, $normalized);
236 51 100       122 if ($ok) {
237 17 50       33 if (@$values) {
238 17         50 push @evaluated, $values->[0];
239             }
240             else {
241 0         0 push @evaluated, undef;
242             }
243             }
244             else {
245 34         45 $all_ok = 0;
246 34         61 last;
247             }
248             }
249              
250 52 100       140 if ($all_ok) {
251 18         30 @$out_ref = @evaluated;
252 18         69 return 1;
253             }
254             }
255              
256             # support for addition (. + expr)
257 1322 100       2852 if ($normalized =~ /^\.\s*\+\s*(.+)$/s) {
258 9         18 my $rhs_expr = $1;
259             @next_results = map {
260 9         14 my $lhs = $_;
  9         9  
261 9         21 my ($rhs_values, $rhs_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $lhs, $rhs_expr);
262 9 50 33     39 my $rhs = ($rhs_ok && @$rhs_values) ? $rhs_values->[0] : undef;
263 9         26 JQ::Lite::Util::_apply_addition($lhs, $rhs);
264             } @results;
265 8         13 @$out_ref = @next_results;
266 8         70 return 1;
267             }
268              
269 1313         2533 my ($add_lhs, $add_rhs) = JQ::Lite::Util::_split_top_level_operator($normalized, '+');
270 1313 100 66     2691 if (defined $add_lhs && defined $add_rhs) {
271             @next_results = map {
272 12         18 my ($values, $ok) = JQ::Lite::Util::_evaluate_value_expression($self, $_, $normalized);
  12         37  
273 10 50 33     42 ($ok && @$values) ? $values->[0] : undef;
274             } @results;
275 10         17 @$out_ref = @next_results;
276 10         31 return 1;
277             }
278              
279             # support for array constructors [expr, expr, ...]
280 1301 100       2963 if ($normalized =~ /^\[(.*)\]$/s) {
281 15         44 my $inner = $1;
282 15         26 my @elements = ();
283 15 100       48 if ($inner =~ /\S/) {
284 14         36 @elements = JQ::Lite::Util::_split_top_level_commas($inner);
285             }
286              
287 15         30 for my $item (@results) {
288 19         31 my @built;
289              
290 19         31 for my $element (@elements) {
291 30 50       60 next if !defined $element;
292 30         140 $element =~ s/^\s+|\s+$//g;
293 30 50       68 next if $element eq '';
294              
295 30         86 my ($values, $ok) = JQ::Lite::Util::_evaluate_value_expression($self, $item, $element);
296 30 100       68 if ($ok) {
297 27 100       46 if (@$values) {
298 25         40 push @built, @$values;
299             } else {
300 2         4 push @built, undef;
301             }
302 27         56 next;
303             }
304              
305 3         22 my $json = JQ::Lite::Util::_encode_json($item);
306 3         211 my @outputs = $self->run_query($json, $element);
307 3 50       7 if (@outputs) {
308 3         8 push @built, @outputs;
309             } else {
310 0         0 push @built, undef;
311             }
312             }
313              
314 19         45 push @next_results, \@built;
315             }
316              
317             # handle empty [] constructor
318 15 100       40 if (!@elements) {
319 1         5 @next_results = map { [] } @results;
  2         7  
320             }
321              
322 15         66 @$out_ref = @next_results;
323 15         98 return 1;
324             }
325              
326             # support for object constructors {key: expr, ...}
327 1286 100       3006 if ($normalized =~ /^\{(.*)\}$/s) {
328 8         20 my $inner = $1;
329 8         12 my @pairs = ();
330 8 100       33 if ($inner =~ /\S/) {
331 7         25 @pairs = JQ::Lite::Util::_split_top_level_commas($inner);
332             }
333              
334 8         15 for my $item (@results) {
335 11         14 my %built;
336              
337 11         13 for my $pair (@pairs) {
338 13 50       21 next if !defined $pair;
339              
340 13         21 my ($raw_key, $raw_expr) = JQ::Lite::Util::_split_top_level_colon($pair);
341 13 50       20 next if !defined $raw_key;
342              
343 13         22 my $key = JQ::Lite::Util::_interpret_object_key($raw_key);
344 13 50       22 next if !defined $key;
345              
346 13 50       26 my $value_expr = defined $raw_expr ? $raw_expr : '';
347 13         68 $value_expr =~ s/^\s+|\s+$//g;
348 13 50       24 next if $value_expr eq '';
349              
350 13         16 my $value;
351              
352 13         29 my ($values, $ok) = JQ::Lite::Util::_evaluate_value_expression($self, $item, $value_expr);
353 13 50       23 if ($ok) {
354 13 100       27 $value = @$values ? $values->[0] : undef;
355             }
356             else {
357 0         0 my $json = JQ::Lite::Util::_encode_json($item);
358 0         0 my @outputs = $self->run_query($json, $value_expr);
359 0 0       0 $value = @outputs ? $outputs[0] : undef;
360             }
361              
362 13         36 $built{$key} = $value;
363             }
364              
365 11         22 push @next_results, \%built;
366             }
367              
368 8 100       17 if (!@pairs) {
369 1         2 @next_results = map { {} } @results;
  2         5  
370             }
371              
372 8         10 @$out_ref = @next_results;
373 8         28 return 1;
374             }
375              
376 1278 100       2623 if (my $foreach = JQ::Lite::Util::_parse_foreach_expression($normalized)) {
377 4         7 @next_results = ();
378              
379 4         6 for my $value (@results) {
380 4         10 my $json = JQ::Lite::Util::_encode_json($value);
381 4         503 my @items = $self->run_query($json, $foreach->{generator});
382              
383 4         10 my ($init_values, $init_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $value, $foreach->{init_expr});
384 4         7 my $acc;
385 4 100       7 if ($init_ok) {
386 3 50       6 $acc = @$init_values ? $init_values->[0] : undef;
387             }
388             else {
389 1         4 my @init_outputs = $self->run_query(JQ::Lite::Util::_encode_json($value), $foreach->{init_expr});
390 1 50       3 $acc = @init_outputs ? $init_outputs[0] : undef;
391             }
392              
393 4         5 for my $element (@items) {
394 14 50       17 my %existing = %{ $self->{_vars} || {} };
  14         33  
395 14         52 local $self->{_vars} = { %existing, $foreach->{var_name} => $element };
396              
397 14         25 my ($updated_values, $updated_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $acc, $foreach->{update_expr});
398 14         17 my $next;
399 14 50       18 if ($updated_ok) {
400 14 50       25 $next = @$updated_values ? $updated_values->[0] : undef;
401             }
402             else {
403 0         0 my @outputs = $self->run_query(JQ::Lite::Util::_encode_json($acc), $foreach->{update_expr});
404 0 0       0 $next = @outputs ? $outputs[0] : undef;
405             }
406              
407 14         14 $acc = $next;
408              
409 14 100 66     36 if (defined $foreach->{extract_expr} && length $foreach->{extract_expr}) {
410 4         8 my ($extract_values, $extract_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $acc, $foreach->{extract_expr});
411 4         5 my $output;
412 4 50       6 if ($extract_ok) {
413 4 50       7 $output = @$extract_values ? $extract_values->[0] : undef;
414             }
415             else {
416 0         0 my @extracted = $self->run_query(JQ::Lite::Util::_encode_json($acc), $foreach->{extract_expr});
417 0 0       0 $output = @extracted ? $extracted[0] : undef;
418             }
419              
420 4         12 push @next_results, $output;
421             }
422             else {
423 10         57 push @next_results, $acc;
424             }
425             }
426             }
427              
428 4         9 @$out_ref = @next_results;
429 4         19 return 1;
430             }
431              
432 1274 100       2666 if (my $if_expr = JQ::Lite::Util::_parse_if_expression($normalized)) {
433 9         17 @next_results = ();
434              
435 9         17 for my $value (@results) {
436 9         23 my $json = JQ::Lite::Util::_encode_json($value);
437 9         781 my $matched = 0;
438              
439 9         11 BRANCH: for my $branch (@{ $if_expr->{branches} }) {
  9         19  
440 11         49 my @cond_results = $self->run_query($json, $branch->{condition});
441 11         19 my $truthy = 0;
442              
443 11         15 for my $cond_value (@cond_results) {
444 11 100       31 if (JQ::Lite::Util::_is_truthy($cond_value)) {
445 3         27 $truthy = 1;
446 3         5 last;
447             }
448             }
449              
450 11 50 66     97 if (!$truthy && !@cond_results) {
451 0 0       0 $truthy = JQ::Lite::Util::_evaluate_condition($value, $branch->{condition}) ? 1 : 0;
452             }
453              
454 11 100       25 next BRANCH unless $truthy;
455              
456 3         9 my ($branch_values, $branch_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $value, $branch->{then});
457              
458 3 100       6 if ($branch_ok) {
459 2         8 push @next_results, @$branch_values;
460             }
461             else {
462 1         10 my @outputs = $self->run_query($json, $branch->{then});
463 1         3 push @next_results, @outputs;
464             }
465              
466 3         4 $matched = 1;
467 3         7 last BRANCH;
468             }
469              
470 9 100       16 next if $matched;
471              
472 6 100       14 if (defined $if_expr->{else}) {
473 5         25 my ($else_values, $else_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $value, $if_expr->{else});
474              
475 5 100       17 if ($else_ok) {
476 2         6 push @next_results, @$else_values;
477             }
478             else {
479 3         13 my @else_outputs = $self->run_query($json, $if_expr->{else});
480 3         7 push @next_results, @else_outputs;
481             }
482             }
483             }
484              
485 9         15 @$out_ref = @next_results;
486 9         42 return 1;
487             }
488              
489 1265 100       2447 if (my $reduce = JQ::Lite::Util::_parse_reduce_expression($normalized)) {
490 4         7 @next_results = ();
491              
492 4         7 for my $value (@results) {
493 4         9 my $json = JQ::Lite::Util::_encode_json($value);
494 4         713 my @items = $self->run_query($json, $reduce->{generator});
495              
496 4         16 my ($init_values, $init_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $value, $reduce->{init_expr});
497 4         7 my $acc;
498 4 100       5 if ($init_ok) {
499 3 50       6 $acc = @$init_values ? $init_values->[0] : undef;
500             }
501             else {
502 1         3 my @init_outputs = $self->run_query(JQ::Lite::Util::_encode_json($value), $reduce->{init_expr});
503 1 50       3 $acc = @init_outputs ? $init_outputs[0] : undef;
504             }
505              
506 4         9 for my $element (@items) {
507 11 50       11 my %existing = %{ $self->{_vars} || {} };
  11         25  
508 11         30 local $self->{_vars} = { %existing, $reduce->{var_name} => $element };
509              
510 11         21 my ($updated_values, $updated_ok) = JQ::Lite::Util::_evaluate_value_expression($self, $acc, $reduce->{update_expr});
511 11         11 my $next;
512 11 50       14 if ($updated_ok) {
513 11 50       18 $next = @$updated_values ? $updated_values->[0] : undef;
514             }
515             else {
516 0         0 my @outputs = $self->run_query(JQ::Lite::Util::_encode_json($acc), $reduce->{update_expr});
517 0 0       0 $next = @outputs ? $outputs[0] : undef;
518             }
519              
520 11         30 $acc = $next;
521             }
522              
523 4         9 push @next_results, $acc;
524             }
525              
526 4         7 @$out_ref = @next_results;
527 4         20 return 1;
528             }
529              
530             # support for .[] iteration
531 1261 100       2546 if ($part eq '.[]') {
532             @next_results = map {
533 14 0       50 ref $_ eq 'ARRAY' ? @$_
  14 50       86  
    100          
534             : ref $_ eq 'HASH' ? values %$_
535             : JQ::Lite::Util::_is_string_scalar($_) ? split(//, "$_")
536             : ()
537             } @results;
538 14         58 @$out_ref = @next_results;
539 14         60 return 1;
540             }
541              
542             # support for select(...)
543 1247 100       2419 if ($part =~ /^select\((.+)\)$/) {
544 35         92 my $cond = $1;
545 35         50 @next_results = ();
546              
547 35         68 my $has_wildcard_array = index($cond, '[]') != -1;
548 35         160 my $has_comparison = ($cond =~ /(==|!=|>=|<=|>|<|\band\b|\bor\b|\bcontains\b|\bhas\b|\bmatch\b)/i);
549 35   100     128 my $use_streaming_eval = $has_wildcard_array || !$has_comparison;
550              
551             # allow built-in filters like match() and test() to run so their errors surface
552 35   100     142 $use_streaming_eval ||= ($cond =~ /^\s*(match|test)\s*\(/);
553              
554 35         51 VALUE: for my $value (@results) {
555 53 100       147 my $simple = JQ::Lite::Util::_evaluate_condition($value, $cond) ? 1 : 0;
556              
557 51 100       93 if ($use_streaming_eval) {
558 6         14 my $json = JQ::Lite::Util::_encode_json($value);
559 6         622 my $error;
560             my @cond_results;
561              
562             {
563 6         26 local $@;
  6         7  
564 6         9 @cond_results = eval { $self->run_query($json, $cond) };
  6         26  
565 6         14 $error = $@;
566             }
567              
568 6 100       27 die $error if $error;
569              
570 4 50       7 if (@cond_results) {
571 4         8 my $truthy = 0;
572              
573 4         6 for my $cond_value (@cond_results) {
574 8 100       29 if (JQ::Lite::Util::_is_truthy($cond_value)) {
575 5         29 $truthy++;
576             }
577             }
578              
579 4 100       12 if ($truthy) {
580 3         6 push @next_results, (($value) x $truthy);
581             }
582              
583 4         16 next VALUE;
584             }
585             }
586              
587 45 100       86 if ($simple) {
588 16         32 push @next_results, $value;
589             }
590             }
591              
592 31         63 @$out_ref = @next_results;
593 31         110 return 1;
594             }
595              
596             # support for length
597 1212 100       2128 if ($part eq 'length') {
598             @next_results = map {
599 20 100 66     82 if (!defined $_) {
  20 100       123  
    100          
    50          
600 1         2 0;
601             }
602             elsif (ref $_ eq 'ARRAY') {
603 13         45 scalar(@$_);
604             }
605             elsif (ref $_ eq 'HASH') {
606 2         7 scalar(keys %$_);
607             }
608             elsif (!ref $_ || ref($_) eq 'JSON::PP::Boolean') {
609 4         30 length("$_");
610             }
611             else {
612 0         0 0;
613             }
614             } @results;
615 20         47 @$out_ref = @next_results;
616 20         68 return 1;
617             }
618              
619             # support for keys
620 1192 100       2145 if ($part eq 'keys') {
621             @next_results = map {
622 5 100       10 if (ref $_ eq 'HASH') {
  5 100       28  
623 2         15 [ sort keys %$_ ];
624             }
625             elsif (ref $_ eq 'ARRAY') {
626 1         2 [ 0 .. $#{$_} ];
  1         4  
627             }
628             else {
629 2         28 die 'keys(): argument must be an object or array';
630             }
631             } @results;
632 3         5 @$out_ref = @next_results;
633 3         11 return 1;
634             }
635              
636             # support for keys_unsorted
637 1187 100 66     3349 if ($part eq 'keys_unsorted' || $part eq 'keys_unsorted()') {
638             @next_results = map {
639 3 100       4 if (ref $_ eq 'HASH') {
  3 100       9  
640 1         5 [ keys %$_ ];
641             }
642             elsif (ref $_ eq 'ARRAY') {
643 1         2 [ 0 .. $#{$_} ];
  1         17  
644             }
645             else {
646 1         3 undef;
647             }
648             } @results;
649 3         5 @$out_ref = @next_results;
650 3         9 return 1;
651             }
652              
653             # support for assignment (e.g., .spec.replicas = 3)
654 1184 100       2249 if (JQ::Lite::Util::_looks_like_assignment($part)) {
655 16         26 my ($path, $value_spec, $operator) = JQ::Lite::Util::_parse_assignment_expression($part);
656              
657             @next_results = map {
658 16         28 JQ::Lite::Util::_apply_assignment($self, $_, $path, $value_spec, $operator)
  16         38  
659             } @results;
660              
661 16         33 @$out_ref = @next_results;
662 16         70 return 1;
663             }
664              
665             # support for sort
666 1168 100       2248 if ($part eq 'sort') {
667             @next_results = map {
668 2 50       5 ref $_ eq 'ARRAY' ? [ sort { JQ::Lite::Util::_smart_cmp()->($a, $b) } @$_ ] : $_
  2         12  
  11         33  
669             } @results;
670 2         3 @$out_ref = @next_results;
671 2         8 return 1;
672             }
673              
674             # support for sort_desc
675 1166 100       1996 if ($part eq 'sort_desc') {
676             @next_results = map {
677 3 50       4 if (ref $_ eq 'ARRAY') {
  3         10  
678 3         6 my $cmp = JQ::Lite::Util::_smart_cmp();
679 3         12 [ sort { $cmp->($b, $a) } @$_ ];
  10         16  
680             }
681             else {
682 0         0 $_;
683             }
684             } @results;
685 3         4 @$out_ref = @next_results;
686 3         10 return 1;
687             }
688              
689             # support for unique
690 1163 100       1964 if ($part eq 'unique') {
691             @next_results = map {
692 1 50       2 ref $_ eq 'ARRAY' ? [ JQ::Lite::Util::_uniq(@$_) ] : $_
  1         7  
693             } @results;
694 1         2 @$out_ref = @next_results;
695 1         15 return 1;
696             }
697              
698             # support for unique_by(path)
699 1162 100       2030 if ($part =~ /^unique_by\((.+?)\)$/) {
700 3         5 my $raw_path = $1;
701 3         7 $raw_path =~ s/^\s+|\s+$//g;
702              
703 3         4 my $key_path = $raw_path;
704 3         6 $key_path =~ s/^['"](.*)['"]$/$1/;
705              
706 3   66     6 my $use_entire_item = ($key_path eq '' || $key_path eq '.');
707 3 100       8 $key_path =~ s/^\.// unless $use_entire_item;
708              
709             @next_results = map {
710 3 50       6 if (ref $_ eq 'ARRAY') {
  3         8  
711 3         4 my %seen;
712             my @deduped;
713              
714 3         11 for my $element (@$_) {
715 12         12 my $key_value;
716              
717 12 100       15 if ($use_entire_item) {
718 5         6 $key_value = $element;
719             } else {
720 7         11 my @values = JQ::Lite::Util::_traverse($element, $key_path);
721 7 50       12 $key_value = @values ? $values[0] : undef;
722             }
723              
724 12         7 my $signature;
725 12 50       12 if (defined $key_value) {
726 12         20 $signature = JQ::Lite::Util::_key($key_value);
727             } else {
728 0         0 $signature = "\0__JQ_LITE_UNDEF__";
729             }
730              
731 12 100       21 next if $seen{$signature}++;
732 8         12 push @deduped, $element;
733             }
734              
735 3         8 \@deduped;
736             } else {
737 0         0 $_;
738             }
739             } @results;
740              
741 3         4 @$out_ref = @next_results;
742 3         10 return 1;
743             }
744              
745             # support for first
746 1159 100       1936 if ($part eq 'first') {
747             @next_results = map {
748 4 50 33     7 ref $_ eq 'ARRAY' && @$_ ? $$_[0] : undef
  3         20  
749             } @results;
750 4         6 @$out_ref = @next_results;
751 4         75 return 1;
752             }
753              
754             # support for last
755 1155 100       1967 if ($part eq 'last') {
756             @next_results = map {
757 2 50 33     4 ref $_ eq 'ARRAY' && @$_ ? $$_[-1] : undef
  1         6  
758             } @results;
759 2         3 @$out_ref = @next_results;
760 2         5 return 1;
761             }
762              
763             # support for rest
764 1153 100       1770 if ($part eq 'rest') {
765             @next_results = map {
766 3         3 ref $_ eq 'ARRAY'
767 3 100       11 ? (@$_ ? [ @$_[ 1 .. $#{$_} ] ] : [])
  1 100       4  
768             : $_
769             } @results;
770 3         4 @$out_ref = @next_results;
771 3         10 return 1;
772             }
773              
774             # support for reverse
775 1150 100       1929 if ($part eq 'reverse') {
776             @next_results = map {
777 4 100       5 ref $_ eq 'ARRAY' ? [ reverse @$_ ]
  4 100       18  
778             : JQ::Lite::Util::_is_string_scalar($_) ? scalar reverse $_
779             : $_
780             } @results;
781 4         7 @$out_ref = @next_results;
782 4         12 return 1;
783             }
784              
785             # support for limit(n)
786 1146 100       2039 if ($part =~ /^limit\((.+)\)$/) {
787 5         11 my $limit_str = $1;
788 5         10 $limit_str =~ s/^\s+|\s+$//g;
789              
790 5 100       19 if ($limit_str !~ /^\d+$/) {
791 2         24 die "limit(): count must be a non-negative integer";
792             }
793              
794 3         5 my $limit = $limit_str + 0;
795              
796             @next_results = map {
797 3 50       6 if (ref $_ eq 'ARRAY') {
  3         5  
798 3         3 my $arr = $_;
799 3         4 my $end = $limit - 1;
800 3 100       6 $end = $#$arr if $end > $#$arr;
801 3         11 [ @$arr[0 .. $end] ]
802             } else {
803 0         0 $_
804             }
805             } @results;
806 3         3 @$out_ref = @next_results;
807 3         10 return 1;
808             }
809              
810             # support for drop(n)
811 1141 100       2239 if ($part =~ /^drop\((.+)\)$/) {
812 6         13 my $count_str = $1;
813 6         8 $count_str =~ s/^\s+|\s+$//g;
814              
815 6 100       34 if ($count_str !~ /^\d+$/) {
816 2         27 die "drop(): count must be a non-negative integer";
817             }
818              
819 4         6 my $count = $count_str + 0;
820             @next_results = map {
821 4 100       6 if (ref $_ eq 'ARRAY') {
  4         7  
822 3         4 my $arr = $_;
823 3 100       10 if ($count >= @$arr) {
824 1         3 [];
825             } else {
826 2         10 [ @$arr[$count .. $#$arr] ];
827             }
828             } else {
829 1         3 $_;
830             }
831             } @results;
832 4         6 @$out_ref = @next_results;
833 4         13 return 1;
834             }
835              
836             # support for tail(n)
837 1135 100       2115 if ($part =~ /^tail\((.+)\)$/) {
838 6         13 my $count_str = $1;
839 6         11 $count_str =~ s/^\s+|\s+$//g;
840              
841 6 100       24 if ($count_str !~ /^\d+$/) {
842 2         31 die "tail(): count must be a non-negative integer";
843             }
844              
845 4         8 my $count = $count_str + 0;
846             @next_results = map {
847 4 100       9 if (ref $_ eq 'ARRAY') {
  4         7  
848 3         5 my $arr = $_;
849              
850 3 100 66     15 if ($count == 0 || !@$arr) {
851 1         3 [];
852             } else {
853 2         4 my $start = @$arr - $count;
854 2 100       5 $start = 0 if $start < 0;
855              
856 2         10 [ @$arr[$start .. $#$arr] ];
857             }
858             } else {
859 1         3 $_;
860             }
861             } @results;
862              
863 4         8 @$out_ref = @next_results;
864 4         17 return 1;
865             }
866              
867             # support for chunks(n)
868 1129 100       2034 if ($part =~ /^chunks\((.+)\)$/) {
869 7         16 my $size_str = $1;
870 7         10 $size_str =~ s/^\s+|\s+$//g;
871              
872 7 100       20 if ($size_str !~ /^\d+$/) {
873 2         24 die "chunks(): size must be a non-negative integer";
874             }
875              
876 5         7 my $size = $size_str + 0;
877 5 100       10 $size = 1 if $size < 1;
878              
879             @next_results = map {
880 5 100       7 if (ref $_ eq 'ARRAY') {
  5         12  
881 4         4 my $arr = $_;
882 4 100       5 if (!@$arr) {
883 1         3 [];
884             } else {
885 3         4 my @chunks;
886 3         6 for (my $i = 0; $i < @$arr; $i += $size) {
887 10         9 my $end = $i + $size - 1;
888 10 100       14 $end = $#$arr if $end > $#$arr;
889 10         24 push @chunks, [ @$arr[$i .. $end] ];
890             }
891 3         7 \@chunks;
892             }
893             } else {
894 1         2 $_;
895             }
896             } @results;
897              
898 5         7 @$out_ref = @next_results;
899 5         23 return 1;
900             }
901              
902             # support for range(...)
903 1122 100       2004 if ($part =~ /^range\((.*)\)$/) {
904 9         20 my $args_raw = $1;
905 9         23 my @args = JQ::Lite::Util::_parse_range_arguments($args_raw);
906              
907 9         14 @next_results = ();
908 9         13 for my $value (@results) {
909 9         16 push @next_results, JQ::Lite::Util::_apply_range($value, \@args);
910             }
911              
912 4         7 @$out_ref = @next_results;
913 4         15 return 1;
914             }
915              
916             # support for map(...)
917 1113 100       1957 if ($part =~ /^map\((.+)\)$/) {
918 29         87 my $filter = $1;
919             @next_results = map {
920 29 50       59 if (ref $_ eq 'ARRAY') {
  29         98  
921 29         42 my @mapped;
922              
923 29         64 for my $element (@$_) {
924 80         173 my @outputs = $self->run_query(JQ::Lite::Util::_encode_json($element), $filter);
925 80 100       206 push @mapped, @outputs if @outputs;
926             }
927              
928 29         87 \@mapped;
929             } else {
930 0         0 $_;
931             }
932             } @results;
933 29         76 @$out_ref = @next_results;
934 29         103 return 1;
935             }
936              
937             # support for map_values(filter)
938 1084 100       1842 if ($part =~ /^map_values\((.+)\)$/) {
939 6         14 my $filter = $1;
940 6         8 @next_results = map { JQ::Lite::Util::_apply_map_values($self, $_, $filter) } @results;
  6         34  
941 6         7 @$out_ref = @next_results;
942 6         17 return 1;
943             }
944              
945             # support for walk(filter)
946 1078 100       1861 if ($part =~ /^walk\((.+)\)$/) {
947 3         8 my $filter = $1;
948 3         5 @next_results = map { JQ::Lite::Util::_apply_walk($self, $_, $filter) } @results;
  3         8  
949 3         5 @$out_ref = @next_results;
950 3         8 return 1;
951             }
952              
953             # support for recurse([filter])
954 1075 100       1788 if ($part =~ /^recurse(?:\((.*)\))?$/) {
955 2 100       8 my $filter = defined $1 ? $1 : '';
956 2         4 $filter =~ s/^\s+|\s+$//g;
957 2 100       4 $filter = undef if $filter eq '';
958              
959 2         4 @next_results = map { JQ::Lite::Util::_apply_recurse($self, $_, $filter) } @results;
  2         41  
960 2         4 @$out_ref = @next_results;
961 2         9 return 1;
962             }
963              
964             # support for enumerate()
965 1073 100       1692 if ($part =~ /^enumerate(?:\(\))?$/) {
966             @next_results = map {
967 4 100       7 if (ref $_ eq 'ARRAY') {
  4         9  
968 3         4 my $arr = $_;
969 3         2 my @pairs;
970 3         8 for my $idx (0 .. $#$arr) {
971 6         14 push @pairs, { index => $idx, value => $arr->[$idx] };
972             }
973 3         7 \@pairs;
974             } else {
975 1         3 $_;
976             }
977             } @results;
978              
979 4         4 @$out_ref = @next_results;
980 4         13 return 1;
981             }
982              
983             # support for to_entries
984 1069 100       1585 if ($part eq 'to_entries') {
985 3         6 @next_results = map { JQ::Lite::Util::_to_entries($_) } @results;
  3         8  
986 3         6 @$out_ref = @next_results;
987 3         9 return 1;
988             }
989              
990             # support for from_entries
991 1066 100       1600 if ($part eq 'from_entries') {
992 10         15 @next_results = map { JQ::Lite::Util::_from_entries($_) } @results;
  10         27  
993 6         14 @$out_ref = @next_results;
994 6         21 return 1;
995             }
996              
997             # support for with_entries(filter)
998 1056 100       1690 if ($part =~ /^with_entries\((.+)\)$/) {
999 1         3 my $filter = $1;
1000 1         2 @next_results = map { JQ::Lite::Util::_apply_with_entries($self, $_, $filter) } @results;
  1         5  
1001 1         2 @$out_ref = @next_results;
1002 1         4 return 1;
1003             }
1004              
1005             # support for transpose()
1006 1055 100 66     3070 if ($part eq 'transpose()' || $part eq 'transpose') {
1007             @next_results = map {
1008 4 50       7 if (ref $_ eq 'ARRAY') {
  4         22  
1009 4         4 my $outer = $_;
1010              
1011 4 100       9 if (!@$outer) {
    100          
1012 1         3 [];
1013             }
1014 7         19 elsif (grep { ref $_ ne 'ARRAY' } @$outer) {
1015 1         3 $_;
1016             }
1017             else {
1018 2         3 my @lengths = map { scalar(@$_) } @$outer;
  4         8  
1019 2 50       10 my $limit = @lengths ? min(@lengths) : 0;
1020              
1021 2 50       4 if ($limit <= 0) {
1022 0         0 [];
1023             } else {
1024 2         3 my @transposed;
1025 2         5 for my $idx (0 .. $limit - 1) {
1026 4         4 push @transposed, [ map { $_->[$idx] } @$outer ];
  8         26  
1027             }
1028 2         5 \@transposed;
1029             }
1030             }
1031             } else {
1032 0         0 $_;
1033             }
1034             } @results;
1035              
1036 4         6 @$out_ref = @next_results;
1037 4         12 return 1;
1038             }
1039              
1040             # support for slice(start[, length])
1041 1051 100       2002 if ($part =~ /^slice(?:\((.*)\))?$/) {
1042 9 50       32 my $args_raw = defined $1 ? $1 : '';
1043 9         22 my @args = JQ::Lite::Util::_parse_arguments($args_raw);
1044              
1045 9         17 @next_results = map { JQ::Lite::Util::_apply_slice($_, @args) } @results;
  9         21  
1046 7         11 @$out_ref = @next_results;
1047 7         28 return 1;
1048             }
1049              
1050             # support for pluck(key)
1051 1042 100       1803 if ($part =~ /^pluck\((.+)\)$/) {
1052 3         8 my $key_path = $1;
1053 3         12 $key_path =~ s/^['"](.*)['"]$/$1/;
1054 3         4 $key_path =~ s/^\.//;
1055              
1056             @next_results = map {
1057 3 50       5 if (ref $_ eq 'ARRAY') {
  3         7  
1058             my @collected = map {
1059 3         5 my $item = $_;
  9         9  
1060 9         29 my @values = JQ::Lite::Util::_traverse($item, $key_path);
1061 9 100       17 @values ? $values[0] : undef;
1062             } @$_;
1063 3         5 \@collected;
1064             } else {
1065 0         0 $_;
1066             }
1067             } @results;
1068              
1069 3         6 @$out_ref = @next_results;
1070 3         8 return 1;
1071             }
1072              
1073             # support for pick(key1, key2, ...)
1074 1039 100       1764 if ($part =~ /^pick\((.*)\)$/) {
1075 3 50       9 my @keys = map { defined $_ ? "$_" : undef } JQ::Lite::Util::_parse_arguments($1);
  5         15  
1076 3         5 @keys = grep { defined $_ } @keys;
  5         12  
1077              
1078 3         6 @next_results = map { JQ::Lite::Util::_apply_pick($_, \@keys) } @results;
  3         9  
1079 3         5 @$out_ref = @next_results;
1080 3         34 return 1;
1081             }
1082              
1083             # support for merge_objects()
1084 1036 100 66     3321 if ($part eq 'merge_objects()' || $part eq 'merge_objects') {
1085 3         7 @next_results = map { JQ::Lite::Util::_apply_merge_objects($_) } @results;
  3         8  
1086 3         5 @$out_ref = @next_results;
1087 3         10 return 1;
1088             }
1089              
1090             # support for add
1091 1033 100       1822 if ($part eq 'add') {
1092             @next_results = map {
1093 1 50       2 ref $_ eq 'ARRAY' ? sum(map { 0 + $_ } @$_) : $_
  1         4  
  3         8  
1094             } @results;
1095 1         2 @$out_ref = @next_results;
1096 1         3 return 1;
1097             }
1098              
1099             # support for sum (alias for add)
1100 1032 100       1688 if ($part eq 'sum') {
1101             @next_results = map {
1102 1 50       2 ref $_ eq 'ARRAY' ? sum(map { 0 + $_ } @$_) : $_
  1         5  
  3         8  
1103             } @results;
1104 1         1 @$out_ref = @next_results;
1105 1         4 return 1;
1106             }
1107              
1108             # support for sum_by(path)
1109 1031 100       1783 if ($part =~ /^sum_by\((.+)\)$/) {
1110 4         10 my $raw_path = $1;
1111 4         10 $raw_path =~ s/^\s+|\s+$//g;
1112 4         10 $raw_path =~ s/^['"](.*)['"]$/$1/;
1113              
1114 4   66     31 my $use_entire_item = ($raw_path eq '' || $raw_path eq '.');
1115 4         5 my $key_path = $raw_path;
1116 4 100       15 $key_path =~ s/^\.// unless $use_entire_item;
1117              
1118             @next_results = map {
1119 4 50       7 if (ref $_ eq 'ARRAY') {
  4         7  
1120 4         4 my $sum = 0;
1121 4         5 my $has_number = 0;
1122              
1123 4         5 for my $element (@$_) {
1124 13 100       26 my @values = $use_entire_item
1125             ? ($element)
1126             : JQ::Lite::Util::_traverse($element, $key_path);
1127              
1128 13         14 for my $value (@values) {
1129 14 100       18 next unless defined $value;
1130              
1131 13         13 my $num = $value;
1132 13 50       14 if (ref($num) eq 'JSON::PP::Boolean') {
1133 0 0       0 $num = $num ? 1 : 0;
1134             }
1135              
1136 13 50       16 next if ref $num;
1137 13 100       28 next unless looks_like_number($num);
1138 12         11 $sum += $num;
1139 12         13 $has_number = 1;
1140             }
1141             }
1142              
1143 4 50       9 $has_number ? $sum : 0;
1144             }
1145             else {
1146 0         0 $_;
1147             }
1148             } @results;
1149              
1150 4         5 @$out_ref = @next_results;
1151 4         13 return 1;
1152             }
1153              
1154             # support for median_by(path)
1155 1027 100       1723 if ($part =~ /^median_by\((.+)\)$/) {
1156 6         19 my ($key_path, $use_entire_item) = JQ::Lite::Util::_normalize_path_argument($1);
1157              
1158             @next_results = map {
1159 6 50       12 if (ref $_ eq 'ARRAY') {
  6         10  
1160 6         5 my @numbers;
1161 6         42 for my $element (@$_) {
1162 19         29 push @numbers, JQ::Lite::Util::_project_numeric_values($element, $key_path, $use_entire_item);
1163             }
1164              
1165 6 100       7 if (@numbers) {
1166 5         17 @numbers = sort { $a <=> $b } @numbers;
  14         20  
1167 5         5 my $count = @numbers;
1168 5         12 my $middle = int($count / 2);
1169 5 100       7 if ($count % 2) {
1170 3         9 $numbers[$middle];
1171             } else {
1172 2         9 ($numbers[$middle - 1] + $numbers[$middle]) / 2;
1173             }
1174             } else {
1175 1         3 undef;
1176             }
1177             }
1178             else {
1179 0         0 $_;
1180             }
1181             } @results;
1182              
1183 6         9 @$out_ref = @next_results;
1184 6         27 return 1;
1185             }
1186              
1187             # support for avg_by(path)
1188 1021 100       1664 if ($part =~ /^avg_by\((.+)\)$/) {
1189 5         14 my ($key_path, $use_entire_item) = JQ::Lite::Util::_normalize_path_argument($1);
1190              
1191             @next_results = map {
1192 5 50       11 if (ref $_ eq 'ARRAY') {
  5         11  
1193 5         6 my $sum = 0;
1194 5         5 my $count = 0;
1195              
1196 5         11 for my $element (@$_) {
1197 13 100       28 my @values = $use_entire_item
1198             ? ($element)
1199             : JQ::Lite::Util::_traverse($element, $key_path);
1200              
1201 13         14 for my $value (@values) {
1202 14 100       19 next unless defined $value;
1203              
1204 13         11 my $num = $value;
1205 13 50       16 if (ref($num) eq 'JSON::PP::Boolean') {
1206 0 0       0 $num = $num ? 1 : 0;
1207             }
1208              
1209 13 50       16 next if ref $num;
1210 13 100       41 next unless looks_like_number($num);
1211 12         12 $sum += $num;
1212 12         14 $count += 1;
1213             }
1214             }
1215              
1216 5 100       17 $count ? $sum / $count : 0;
1217             }
1218             else {
1219 0         0 $_;
1220             }
1221             } @results;
1222              
1223 5         14 @$out_ref = @next_results;
1224 5         24 return 1;
1225             }
1226              
1227             # support for max_by(path)
1228 1016 100       1654 if ($part =~ /^max_by\((.+)\)$/) {
1229 3         9 my ($key_path, $use_entire_item) = JQ::Lite::Util::_normalize_path_argument($1);
1230              
1231             @next_results = map {
1232 3 50       5 if (ref $_ eq 'ARRAY') {
  3         8  
1233 3         8 JQ::Lite::Util::_extreme_by($_, $key_path, $use_entire_item, 'max');
1234             } else {
1235 0         0 $_;
1236             }
1237             } @results;
1238              
1239 3         4 @$out_ref = @next_results;
1240 3         10 return 1;
1241             }
1242              
1243             # support for min_by(path)
1244 1013 100       1623 if ($part =~ /^min_by\((.+)\)$/) {
1245 3         9 my ($key_path, $use_entire_item) = JQ::Lite::Util::_normalize_path_argument($1);
1246              
1247             @next_results = map {
1248 3 50       4 if (ref $_ eq 'ARRAY') {
  3         8  
1249 3         7 JQ::Lite::Util::_extreme_by($_, $key_path, $use_entire_item, 'min');
1250             } else {
1251 0         0 $_;
1252             }
1253             } @results;
1254              
1255 3         6 @$out_ref = @next_results;
1256 3         10 return 1;
1257             }
1258              
1259             # support for product
1260 1010 100       1699 if ($part eq 'product') {
1261             @next_results = map {
1262 1 50       3 if (ref $_ eq 'ARRAY') {
  1         5  
1263 1         1 my $product = 1;
1264 1         2 my $has_values = 0;
1265 1         2 for my $val (@$_) {
1266 3 50       5 next unless defined $val;
1267 3         4 $product *= (0 + $val);
1268 3         4 $has_values = 1;
1269             }
1270 1 50       3 $has_values ? $product : 1;
1271             } else {
1272 0         0 $_;
1273             }
1274             } @results;
1275 1         2 @$out_ref = @next_results;
1276 1         4 return 1;
1277             }
1278              
1279             # support for min
1280 1009 100       1541 if ($part eq 'min') {
1281             @next_results = map {
1282 3 50       7 ref $_ eq 'ARRAY' ? do {
  3         8  
1283 3         9 my @numbers = JQ::Lite::Util::_extract_numeric_values($_);
1284 3 100       20 @numbers ? min(@numbers) : undef;
1285             } : $_
1286             } @results;
1287 3         5 @$out_ref = @next_results;
1288 3         9 return 1;
1289             }
1290              
1291             # support for max
1292 1006 100       1589 if ($part eq 'max') {
1293             @next_results = map {
1294 3 50       6 ref $_ eq 'ARRAY' ? do {
  3         9  
1295 3         5 my @numbers = JQ::Lite::Util::_extract_numeric_values($_);
1296 3 100       33 @numbers ? max(@numbers) : undef;
1297             } : $_
1298             } @results;
1299 3         6 @$out_ref = @next_results;
1300 3         8 return 1;
1301             }
1302              
1303             # support for avg
1304 1003 100       1627 if ($part eq 'avg') {
1305             @next_results = map {
1306 1 50 33     3 ref $_ eq 'ARRAY' && @$_ ? sum(map { 0 + $_ } @$_) / scalar(@$_) : 0
  1         6  
  3         9  
1307             } @results;
1308 1         2 @$out_ref = @next_results;
1309 1         4 return 1;
1310             }
1311              
1312             # support for abs
1313 1002 100       1603 if ($part eq 'abs') {
1314             @next_results = map {
1315 2 50       3 if (!defined $_) {
  2 100       9  
    50          
1316 0         0 undef;
1317             }
1318             elsif (!ref $_) {
1319 1 50       14 looks_like_number($_) ? abs($_) : $_;
1320             }
1321             elsif (ref $_ eq 'ARRAY') {
1322 1 100       2 [ map { looks_like_number($_) ? abs($_) : $_ } @$_ ];
  4         13  
1323             }
1324             else {
1325 0         0 $_;
1326             }
1327             } @results;
1328 2         4 @$out_ref = @next_results;
1329 2         8 return 1;
1330             }
1331              
1332             # support for ceil()
1333 1000 100 66     2479 if ($part eq 'ceil()' || $part eq 'ceil') {
1334 4         9 @next_results = map { JQ::Lite::Util::_apply_numeric_function($_, \&JQ::Lite::Util::_ceil) } @results;
  4         17  
1335 4         11 @$out_ref = @next_results;
1336 4         18 return 1;
1337             }
1338              
1339             # support for floor()
1340 996 100 66     2925 if ($part eq 'floor()' || $part eq 'floor') {
1341 5         15 @next_results = map { JQ::Lite::Util::_apply_numeric_function($_, \&JQ::Lite::Util::_floor) } @results;
  5         26  
1342 5         14 @$out_ref = @next_results;
1343 5         25 return 1;
1344             }
1345              
1346             # support for round()
1347 991 100 66     2625 if ($part eq 'round()' || $part eq 'round') {
1348 6         9 @next_results = map { JQ::Lite::Util::_apply_numeric_function($_, \&JQ::Lite::Util::_round) } @results;
  6         15  
1349 6         11 @$out_ref = @next_results;
1350 6         18 return 1;
1351             }
1352              
1353             # support for clamp(min, max)
1354 985 100       1708 if ($part =~ /^clamp\((.*)\)$/) {
1355 10         27 my @args = JQ::Lite::Util::_parse_arguments($1);
1356 10 50       27 my $min = @args ? JQ::Lite::Util::_normalize_numeric_bound($args[0]) : undef;
1357 10 100       16 my $max = @args > 1 ? JQ::Lite::Util::_normalize_numeric_bound($args[1]) : undef;
1358              
1359 10 100 100     32 if (defined $min && defined $max && $min > $max) {
      100        
1360 1         3 ($min, $max) = ($max, $min);
1361             }
1362              
1363 10         15 @next_results = map { JQ::Lite::Util::_apply_clamp($_, $min, $max) } @results;
  9         16  
1364 10         16 @$out_ref = @next_results;
1365 10         33 return 1;
1366             }
1367              
1368             # support for tostring()
1369 975 100 66     2540 if ($part eq 'tostring()' || $part eq 'tostring') {
1370 8         14 @next_results = map { JQ::Lite::Util::_apply_tostring($_) } @results;
  7         22  
1371 8         388 @$out_ref = @next_results;
1372 8         34 return 1;
1373             }
1374              
1375             # support for tojson()
1376 967 100 66     2694 if ($part eq 'tojson()' || $part eq 'tojson') {
1377 10         18 @next_results = map { JQ::Lite::Util::_apply_tojson($_) } @results;
  9         27  
1378 10         638 @$out_ref = @next_results;
1379 10         33 return 1;
1380             }
1381              
1382             # support for fromjson()
1383 957 100 66     2577 if ($part eq 'fromjson()' || $part eq 'fromjson') {
1384 5         9 @next_results = map { JQ::Lite::Util::_apply_fromjson($_) } @results;
  5         13  
1385 5         24 @$out_ref = @next_results;
1386 5         24 return 1;
1387             }
1388              
1389             # support for to_number()
1390 952 100 66     2382 if ($part eq 'to_number()' || $part eq 'to_number') {
1391 8         11 @next_results = map { JQ::Lite::Util::_apply_to_number($_) } @results;
  7         16  
1392 8         23 @$out_ref = @next_results;
1393 8         25 return 1;
1394             }
1395              
1396 944 100 66     2391 if ($part eq 'tonumber()' || $part eq 'tonumber') {
1397 1         2 @next_results = map { JQ::Lite::Util::_tonumber($_) } @results;
  2         14  
1398 1         2 @$out_ref = @next_results;
1399 1         4 return 1;
1400             }
1401              
1402             # support for median
1403 943 100       1602 if ($part eq 'median') {
1404             @next_results = map {
1405 5 50 33     9 if (ref $_ eq 'ARRAY' && @$_) {
  5         26  
1406 5         15 my @numbers = sort { $a <=> $b }
  10         36  
1407             JQ::Lite::Util::_extract_numeric_values($_);
1408              
1409 5 100       13 if (@numbers) {
1410 4         4 my $count = @numbers;
1411 4         9 my $middle = int($count / 2);
1412 4 100       7 if ($count % 2) {
1413 2         5 $numbers[$middle];
1414             } else {
1415 2         7 ($numbers[$middle - 1] + $numbers[$middle]) / 2;
1416             }
1417             } else {
1418 1         3 undef;
1419             }
1420             } else {
1421 0         0 $_;
1422             }
1423             } @results;
1424 5         9 @$out_ref = @next_results;
1425 5         16 return 1;
1426             }
1427              
1428             # support for percentile(p)
1429 938 100       1630 if ($part =~ /^percentile(?:\((.*)\))?$/) {
1430 17 100       51 my $args_raw = defined $1 ? $1 : '';
1431 17 100       49 my @args = length $args_raw ? JQ::Lite::Util::_parse_arguments($args_raw) : ();
1432 17 100       41 my $fraction = @args ? JQ::Lite::Util::_normalize_percentile($args[0]) : 0.5;
1433              
1434             @next_results = map {
1435 17 50 33     28 if (ref $_ eq 'ARRAY' && @$_) {
  17         41  
1436 17         32 my @numbers = sort { $a <=> $b }
  92         168  
1437             JQ::Lite::Util::_extract_numeric_values($_);
1438              
1439 17 100       33 if (@numbers) {
1440 15 100       30 defined $fraction ? JQ::Lite::Util::_percentile_value(\@numbers, $fraction) : undef;
1441             }
1442             else {
1443 2         4 undef;
1444             }
1445             }
1446             else {
1447 0         0 $_;
1448             }
1449             } @results;
1450              
1451 17         23 @$out_ref = @next_results;
1452 17         59 return 1;
1453             }
1454              
1455             # support for mode
1456 921 100       1475 if ($part eq 'mode') {
1457             @next_results = map {
1458 6 50       10 if (ref $_ eq 'ARRAY') {
  6         14  
1459 6 100       11 if (!@$_) {
1460 1         5 undef;
1461             } else {
1462 5         7 my %counts;
1463             my %values;
1464 5         0 my %first_index;
1465 5         5 my $max_count = 0;
1466 5         6 my $best_index = undef;
1467 5         5 my $mode_key;
1468              
1469 5         6 for (my $i = 0; $i < @{$_}; $i++) {
  25         39  
1470 20         20 my $item = $_->[$i];
1471 20 100       28 next unless defined $item;
1472              
1473 18         30 my $key = JQ::Lite::Util::_key($item);
1474 18 50       26 next unless defined $key;
1475              
1476 18         27 $counts{$key}++;
1477 18   66     46 $values{$key} //= $item;
1478 18   66     35 $first_index{$key} //= $i;
1479              
1480 18         20 my $count = $counts{$key};
1481 18         20 my $index = $first_index{$key};
1482              
1483 18 100 100     59 if (!defined $mode_key
      33        
      66        
      66        
1484             || $count > $max_count
1485             || ($count == $max_count
1486             && (!defined $best_index || $index < $best_index))) {
1487 11         12 $mode_key = $key;
1488 11         12 $max_count = $count;
1489 11         14 $best_index = $index;
1490             }
1491             }
1492              
1493 5 50       21 defined $mode_key ? $values{$mode_key} : undef;
1494             }
1495             } else {
1496 0         0 $_;
1497             }
1498             } @results;
1499              
1500 6         13 @$out_ref = @next_results;
1501 6         24 return 1;
1502             }
1503              
1504             # support for variance
1505 915 100       1453 if ($part eq 'variance') {
1506             @next_results = map {
1507 3 50       5 if (ref $_ eq 'ARRAY') {
  3         9  
1508 3         10 my @numbers = JQ::Lite::Util::_extract_numeric_values($_);
1509              
1510 3 100       17 if (@numbers) {
1511 2         41 my $mean = sum(@numbers) / @numbers;
1512 2         4 sum(map { ($_ - $mean) ** 2 } @numbers) / @numbers;
  4         12  
1513             }
1514             else {
1515 1         3 undef;
1516             }
1517             }
1518             else {
1519 0         0 $_;
1520             }
1521             } @results;
1522              
1523 3         6 @$out_ref = @next_results;
1524 3         9 return 1;
1525             }
1526              
1527             # support for stddev
1528 912 100       1501 if ($part eq 'stddev') {
1529             @next_results = map {
1530 3 50       6 if (ref $_ eq 'ARRAY') {
  3         9  
1531 3         7 my @numbers = JQ::Lite::Util::_extract_numeric_values($_);
1532              
1533 3 100       17 if (@numbers) {
1534 2         19 my $mean = sum(@numbers) / @numbers;
1535 2         3 my $variance = sum(map { ($_ - $mean) ** 2 } @numbers) / @numbers;
  4         10  
1536 2         5 sqrt($variance);
1537             }
1538             else {
1539 1         3 undef;
1540             }
1541             }
1542             else {
1543 0         0 $_;
1544             }
1545             } @results;
1546              
1547 3         6 @$out_ref = @next_results;
1548 3         10 return 1;
1549             }
1550              
1551             # support for group_count(key)
1552 909 100       1432 if ($part =~ /^group_count\((.+)\)$/) {
1553 4         10 my $key_path = $1;
1554             @next_results = map {
1555 4         7 JQ::Lite::Util::_group_count($_, $key_path)
  4         10  
1556             } @results;
1557 4         8 @$out_ref = @next_results;
1558 4         14 return 1;
1559             }
1560              
1561             # support for group_by(key)
1562 905 100       1525 if ($part =~ /^group_by\((.+)\)$/) {
1563 5         12 my $key_path = $1;
1564             @next_results = map {
1565 5         8 JQ::Lite::Util::_group_by($_, $key_path)
  5         14  
1566             } @results;
1567 4         5 @$out_ref = @next_results;
1568 4         16 return 1;
1569             }
1570              
1571             # support for count
1572 900 100       1598 if ($part eq 'count') {
1573             @next_results = map {
1574 6 100       10 if (ref $_ eq 'ARRAY') {
  12 100       21  
1575 3         6 scalar(@$_);
1576             }
1577             elsif (!defined $_) {
1578 1         2 0;
1579             }
1580             else {
1581 8         15 1; # count as 1 item for scalars and objects
1582             }
1583             } @results;
1584 6         9 @$out_ref = @next_results;
1585 6         20 return 1;
1586             }
1587              
1588             # support for all() / all(expr)
1589 894 100       1571 if ($part =~ /^all(?:\((.*)\))?$/) {
1590 6 100       22 my $expr = defined $1 ? $1 : undef;
1591 6 50 66     17 $expr = undef if defined($expr) && $expr eq '';
1592              
1593 6         11 @next_results = map { JQ::Lite::Util::_apply_all($self, $_, $expr) } @results;
  6         18  
1594 6         37 @$out_ref = @next_results;
1595 6         27 return 1;
1596             }
1597              
1598             # support for any() / any(expr)
1599 888 100       1668 if ($part =~ /^any(?:\((.*)\))?$/) {
1600 6 100       18 my $expr = defined $1 ? $1 : undef;
1601 6 50 66     18 $expr = undef if defined($expr) && $expr eq '';
1602              
1603 6         8 @next_results = map { JQ::Lite::Util::_apply_any($self, $_, $expr) } @results;
  6         23  
1604 6         45 @$out_ref = @next_results;
1605 6         20 return 1;
1606             }
1607              
1608             # support for join(", ")
1609 882 100       1597 if ($part =~ /^join\((.*?)\)$/) {
1610 8         29 my $sep = JQ::Lite::Util::_parse_string_argument($1);
1611              
1612             @next_results = map {
1613 8 100       12 die 'join(): input must be an array' if ref($_) ne 'ARRAY';
  8         28  
1614 7         5 my @parts;
1615 7         11 for my $item (@$_) {
1616 20 100       24 if (!defined $item) {
1617 2         4 push @parts, '';
1618 2         4 next;
1619             }
1620              
1621 18 100       24 if (ref($item) eq 'JSON::PP::Boolean') {
1622 2 100       24 push @parts, $item ? 'true' : 'false';
1623 2         14 next;
1624             }
1625              
1626 16 100       19 if (ref $item) {
1627 2         25 die 'join(): array elements must be scalars';
1628             }
1629              
1630 14         19 push @parts, "$item";
1631             }
1632              
1633 5         18 join($sep, @parts)
1634             } @results;
1635 5         8 @$out_ref = @next_results;
1636 5         16 return 1;
1637             }
1638              
1639             # support for sort_by(key)
1640 874 100       1335 if ($part =~ /^sort_by\((.+?)\)$/) {
1641 2         7 my $key_path = $1;
1642 2         5 $key_path =~ s/^\.//; # Remove leading dot
1643            
1644 2         14 my $cmp = JQ::Lite::Util::_smart_cmp();
1645 2         4 @next_results = ();
1646            
1647 2         8 for my $item (@results) {
1648 2 50       8 if (ref $item eq 'ARRAY') {
1649             my @sorted = sort {
1650 2   50     11 my $a_val = (JQ::Lite::Util::_traverse($a, $key_path))[0] // '';
  5         11  
1651 5   50     9 my $b_val = (JQ::Lite::Util::_traverse($b, $key_path))[0] // '';
1652              
1653 5         8 $cmp->($a_val, $b_val);
1654             } @$item;
1655            
1656 2         4 push @next_results, \@sorted;
1657             } else {
1658 0         0 push @next_results, $item;
1659             }
1660             }
1661            
1662 2         8 @$out_ref = @next_results;
1663 2         18 return 1;
1664             }
1665              
1666             # support for empty
1667 872 100       1527 if ($part eq 'empty') {
1668 13         25 @results = (); # discard all results
1669 13         56 return 1;
1670             }
1671              
1672             # support for values
1673 859 100       1493 if ($part eq 'values') {
1674             @next_results = map {
1675 1 50       2 ref $_ eq 'HASH' ? [ values %$_ ] : $_
  1         7  
1676             } @results;
1677 1         2 @$out_ref = @next_results;
1678 1         4 return 1;
1679             }
1680              
1681             # support for arrays
1682 858 100 66     2317 if ($part eq 'arrays()' || $part eq 'arrays') {
1683             @next_results = map {
1684 3 100       6 ref $_ eq 'ARRAY' ? $_ : ()
  8         18  
1685             } @results;
1686 3         4 @$out_ref = @next_results;
1687 3         13 return 1;
1688             }
1689              
1690             # support for scalars
1691 855 100 66     2299 if ($part eq 'scalars()' || $part eq 'scalars') {
1692             @next_results = map {
1693 3 100 100     4 if (!defined $_) {
  8 100       24  
1694 1         1 undef;
1695             }
1696             elsif (!ref $_ || ref($_) eq 'JSON::PP::Boolean') {
1697 4         6 $_;
1698             }
1699             else {
1700 3         5 ();
1701             }
1702             } @results;
1703 3         5 @$out_ref = @next_results;
1704 3         18 return 1;
1705             }
1706              
1707             # support for objects
1708 852 100 66     2453 if ($part eq 'objects()' || $part eq 'objects') {
1709             @next_results = map {
1710 4 100       9 ref $_ eq 'HASH' ? $_ : ()
  9         24  
1711             } @results;
1712 4         37 @$out_ref = @next_results;
1713 4         20 return 1;
1714             }
1715              
1716             # support for flatten()
1717 848 100 66     2196 if ($part eq 'flatten()' || $part eq 'flatten') {
1718             @next_results = map {
1719 5 100       7 ref $_ eq 'ARRAY'
  5         21  
1720             ? JQ::Lite::Util::_flatten_depth($_, 1)
1721             : $_
1722             } @results;
1723 5         9 @$out_ref = @next_results;
1724 5         17 return 1;
1725             }
1726              
1727             # support for flatten_all()
1728 843 100 66     2213 if ($part eq 'flatten_all()' || $part eq 'flatten_all') {
1729             @next_results = map {
1730 2 50       3 if (ref $_ eq 'ARRAY') {
  2         5  
1731 2         7 JQ::Lite::Util::_flatten_all($_);
1732             } else {
1733 0         0 $_;
1734             }
1735             } @results;
1736 2         4 @$out_ref = @next_results;
1737 2         12 return 1;
1738             }
1739              
1740             # support for flatten_depth(n)
1741 841 100       1354 if ($part =~ /^flatten_depth(?:\((.*)\))?$/) {
1742 6 100       19 my $args_raw = defined $1 ? $1 : '';
1743 6 100       19 my @args = length $args_raw ? JQ::Lite::Util::_parse_arguments($args_raw) : ();
1744 6 100       10 my $depth = @args ? $args[0] : 1;
1745              
1746 6 50 33     24 if (!defined $depth || !looks_like_number($depth)) {
1747 0         0 $depth = 1;
1748             }
1749              
1750 6         7 $depth = int($depth);
1751 6 50       9 $depth = 0 if $depth < 0;
1752              
1753             @next_results = map {
1754 6 100       16 if (ref $_ eq 'ARRAY') {
  6         10  
1755 5         12 JQ::Lite::Util::_flatten_depth($_, $depth);
1756             } else {
1757 1         3 $_;
1758             }
1759             } @results;
1760              
1761 6         8 @$out_ref = @next_results;
1762 6         21 return 1;
1763             }
1764              
1765             # support for type()
1766 835 100 66     2270 if ($part eq 'type()' || $part eq 'type') {
1767             @next_results = map {
1768 9 100       21 if (!defined $_) {
  9 100       27  
    100          
    100          
    50          
    100          
1769 1         3 'null';
1770             }
1771             elsif (ref($_) eq 'ARRAY') {
1772 1         2 'array';
1773             }
1774             elsif (ref($_) eq 'HASH') {
1775 1         3 'object';
1776             }
1777             elsif (ref($_) eq '') {
1778 5         20 my $sv = B::svref_2object(\$_);
1779 5         20 my $flags = $sv->FLAGS;
1780              
1781 5 100       33 ($flags & (SVp_IOK | SVp_NOK)) ? 'number' : 'string';
1782             }
1783             elsif (ref($_) eq 'JSON::PP::Boolean') {
1784 1         2 'boolean';
1785             }
1786             else {
1787 0         0 'unknown';
1788             }
1789             } (@results ? @results : (undef));
1790 9         15 @$out_ref = @next_results;
1791 9         27 return 1;
1792             }
1793              
1794             # support for nth(n)
1795 826 100       1412 if ($part =~ /^nth\((\d+)\)$/) {
1796 4         9 my $index = $1;
1797             @next_results = map {
1798 4 50       6 if (ref $_ eq 'ARRAY') {
  4         9  
1799 4         12 $_->[$index]
1800             } else {
1801             undef
1802 0         0 }
1803             } @results;
1804 4         48 @$out_ref = @next_results;
1805 4         15 return 1;
1806             }
1807              
1808             # support for del(key)
1809 822 100       1369 if ($part =~ /^del\((.+?)\)$/) {
1810 5         11 my $key = $1;
1811 5         14 $key =~ s/^\s+|\s+$//g; # allow whitespace around argument
1812 5         21 $key =~ s/^['"](.*?)['"]$/$1/; # remove quotes
1813              
1814             @next_results = map {
1815 5 100       10 if (ref $_ eq 'HASH') {
  5         9  
1816 4         29 my %copy = %$_; # shallow copy
1817 4         8 delete $copy{$key};
1818 4         17 \%copy
1819             } else {
1820 1         3 $_
1821             }
1822             } @results;
1823 5         11 @$out_ref = @next_results;
1824 5         16 return 1;
1825             }
1826              
1827             # support for delpaths(paths_expr)
1828 817 100       1350 if ($part =~ /^delpaths\((.*)\)$/) {
1829 10         26 my $filter = $1;
1830 10         38 $filter =~ s/^\s+|\s+$//g;
1831              
1832 10         20 @next_results = map { JQ::Lite::Util::_apply_delpaths($self, $_, $filter) } @results;
  10         32  
1833 5         10 @$out_ref = @next_results;
1834 5         16 return 1;
1835             }
1836              
1837             # support for compact()
1838 807 100 100     2106 if ($part eq 'compact()' || $part eq 'compact') {
1839             @next_results = map {
1840 3 100       6 if (ref $_ eq 'ARRAY') {
  3         7  
1841 2         3 [ grep { defined $_ } @$_ ]
  10         14  
1842             } else {
1843 1         4 $_
1844             }
1845             } @results;
1846 3         7 @$out_ref = @next_results;
1847 3         26 return 1;
1848             }
1849              
1850             # support for titlecase()
1851 804 100 66     2047 if ($part eq 'titlecase()' || $part eq 'titlecase') {
1852 3         5 @next_results = map { JQ::Lite::Util::_apply_case_transform($_, 'titlecase') } @results;
  4         9  
1853 3         6 @$out_ref = @next_results;
1854 3         10 return 1;
1855             }
1856              
1857             # support for upper()
1858 801 100 66     2067 if ($part eq 'upper()' || $part eq 'upper') {
1859 10         19 @next_results = map { JQ::Lite::Util::_apply_case_transform($_, 'upper') } @results;
  10         27  
1860 10         21 @$out_ref = @next_results;
1861 10         31 return 1;
1862             }
1863              
1864             # support for ascii_upcase
1865 791 100 66     2008 if ($part eq 'ascii_upcase()' || $part eq 'ascii_upcase') {
1866 3         6 @next_results = map { JQ::Lite::Util::_apply_ascii_case_transform($_, 'upper') } @results;
  3         8  
1867 3         6 @$out_ref = @next_results;
1868 3         9 return 1;
1869             }
1870              
1871             # support for ascii_downcase
1872 788 100 66     1942 if ($part eq 'ascii_downcase()' || $part eq 'ascii_downcase') {
1873 3         5 @next_results = map { JQ::Lite::Util::_apply_ascii_case_transform($_, 'lower') } @results;
  3         8  
1874 3         5 @$out_ref = @next_results;
1875 3         10 return 1;
1876             }
1877              
1878             # support for lower()
1879 785 100 66     2042 if ($part eq 'lower()' || $part eq 'lower') {
1880 7         12 @next_results = map { JQ::Lite::Util::_apply_case_transform($_, 'lower') } @results;
  8         18  
1881 7         16 @$out_ref = @next_results;
1882 7         20 return 1;
1883             }
1884              
1885             # support for trim()
1886 778 100 66     1992 if ($part eq 'trim()' || $part eq 'trim') {
1887 8         12 @next_results = map { JQ::Lite::Util::_apply_trim($_) } @results;
  7         22  
1888 8         13 @$out_ref = @next_results;
1889 8         26 return 1;
1890             }
1891              
1892             # support for ltrimstr("prefix")
1893 770 100       1252 if ($part =~ /^ltrimstr\((.+)\)$/) {
1894 10         24 my $needle = JQ::Lite::Util::_parse_string_argument($1);
1895 10         16 @next_results = map { JQ::Lite::Util::_apply_trimstr($_, $needle, 'left') } @results;
  10         23  
1896 10         16 @$out_ref = @next_results;
1897 10         32 return 1;
1898             }
1899              
1900             # support for rtrimstr("suffix")
1901 760 100       1263 if ($part =~ /^rtrimstr\((.+)\)$/) {
1902 8         20 my $needle = JQ::Lite::Util::_parse_string_argument($1);
1903 8         13 @next_results = map { JQ::Lite::Util::_apply_trimstr($_, $needle, 'right') } @results;
  8         22  
1904 8         13 @$out_ref = @next_results;
1905 8         40 return 1;
1906             }
1907              
1908             # support for has(key)
1909 752 100       1300 if ($part =~ /^has\((.+)\)$/) {
1910 7         41 my @args = JQ::Lite::Util::_parse_arguments($1);
1911 7 50       17 my $needle = @args ? $args[0] : undef;
1912              
1913 7         14 @next_results = map { JQ::Lite::Util::_apply_has($_, $needle) } @results;
  7         22  
1914 7         60 @$out_ref = @next_results;
1915 7         31 return 1;
1916             }
1917              
1918             # support for contains(value)
1919 745 100       1200 if ($part =~ /^contains\((.+)\)$/) {
1920 10         24 my $needle = JQ::Lite::Util::_parse_literal_argument($1);
1921 10         17 @next_results = map { JQ::Lite::Util::_apply_contains($_, $needle) } @results;
  10         29  
1922 10         38 @$out_ref = @next_results;
1923 10         36 return 1;
1924             }
1925              
1926             # support for contains_subset(value)
1927 735 100       1191 if ($part =~ /^contains_subset\((.+)\)$/) {
1928 11         29 my $needle = JQ::Lite::Util::_parse_literal_argument($1);
1929 11         16 @next_results = map { JQ::Lite::Util::_apply_contains_subset($_, $needle) } @results;
  11         23  
1930 11         45 @$out_ref = @next_results;
1931 11         44 return 1;
1932             }
1933              
1934             # support for inside(container)
1935 724 100       1268 if ($part =~ /^inside\((.+)\)$/) {
1936 7         19 my $container = JQ::Lite::Util::_parse_literal_argument($1);
1937 7         11 @next_results = map { JQ::Lite::Util::_apply_inside($_, $container) } @results;
  7         13  
1938 7         34 @$out_ref = @next_results;
1939 7         31 return 1;
1940             }
1941              
1942             # support for test("pattern"[, "flags"])
1943 717 100       1236 if ($part =~ /^test\((.+)\)$/) {
1944 17         45 my ($pattern_expr, $flags_expr) = JQ::Lite::Util::_split_semicolon_arguments($1, 2);
1945 17 50       50 my $pattern = defined $pattern_expr ? JQ::Lite::Util::_parse_string_argument($pattern_expr) : '';
1946 17 100       30 my $flags = defined $flags_expr ? JQ::Lite::Util::_parse_string_argument($flags_expr) : '';
1947              
1948 17         30 @next_results = map { JQ::Lite::Util::_apply_test($_, $pattern, $flags) } @results;
  16         38  
1949 14         66 @$out_ref = @next_results;
1950 14         53 return 1;
1951             }
1952              
1953             # support for match("pattern"[, "flags"])
1954 700 100       1200 if ($part =~ /^match\((.+)\)$/) {
1955 10         36 my ($pattern_expr, $flags_expr) = JQ::Lite::Util::_split_semicolon_arguments($1, 2);
1956 10 50       28 my $pattern = defined $pattern_expr ? JQ::Lite::Util::_parse_string_argument($pattern_expr) : '';
1957 10 100       23 my $flags = defined $flags_expr ? JQ::Lite::Util::_parse_string_argument($flags_expr) : '';
1958              
1959 10         19 @next_results = map { JQ::Lite::Util::_apply_match($_, $pattern, $flags) } @results;
  10         21  
1960 7         13 @$out_ref = @next_results;
1961 7         59 return 1;
1962             }
1963              
1964             # support for startswith("prefix")
1965 690 100       1194 if ($part =~ /^startswith\((.+)\)$/) {
1966 10         27 my $needle = JQ::Lite::Util::_parse_string_argument($1);
1967 10         17 @next_results = map { JQ::Lite::Util::_apply_string_predicate($_, $needle, 'start') } @results;
  9         17  
1968 10         55 @$out_ref = @next_results;
1969 10         30 return 1;
1970             }
1971              
1972             # support for endswith("suffix")
1973 680 100       1623 if ($part =~ /^endswith\((.+)\)$/) {
1974 9         21 my $needle = JQ::Lite::Util::_parse_string_argument($1);
1975 9         16 @next_results = map { JQ::Lite::Util::_apply_string_predicate($_, $needle, 'end') } @results;
  9         13  
1976 9         36 @$out_ref = @next_results;
1977 9         28 return 1;
1978             }
1979              
1980             # support for explode()
1981 671 100 66     1980 if ($part eq 'explode()' || $part eq 'explode') {
1982 5         8 @next_results = map { JQ::Lite::Util::_apply_explode($_) } @results;
  5         12  
1983 5         9 @$out_ref = @next_results;
1984 5         13 return 1;
1985             }
1986              
1987             # support for implode()
1988 666 100 66     1809 if ($part eq 'implode()' || $part eq 'implode') {
1989 4         8 @next_results = map { JQ::Lite::Util::_apply_implode($_) } @results;
  4         11  
1990 4         8 @$out_ref = @next_results;
1991 4         11 return 1;
1992             }
1993              
1994             # support for replace(old, new)
1995 662 100       1100 if ($part =~ /^replace\((.+)\)$/) {
1996 12         31 my ($search, $replacement) = JQ::Lite::Util::_parse_arguments($1);
1997 12 50       18 $search = defined $search ? $search : '';
1998 12 50       17 $replacement = defined $replacement ? $replacement : '';
1999              
2000 12         18 @next_results = map { JQ::Lite::Util::_apply_replace($_, $search, $replacement) } @results;
  11         27  
2001 12         24 @$out_ref = @next_results;
2002 12         47 return 1;
2003             }
2004              
2005             # support for @json (format value as JSON string)
2006 650 100 66     1739 if ($part eq '@json' || $part eq '@json()') {
2007 11         15 @next_results = map { JQ::Lite::Util::_apply_tojson($_) } @results;
  12         84  
2008 11         583 @$out_ref = @next_results;
2009 11         35 return 1;
2010             }
2011              
2012             # support for @csv (format array/scalar as CSV row)
2013 639 100 66     1795 if ($part eq '@csv' || $part eq '@csv()') {
2014 3         7 @next_results = map { JQ::Lite::Util::_apply_csv($_) } @results;
  3         386  
2015 3         7 @$out_ref = @next_results;
2016 3         13 return 1;
2017             }
2018              
2019             # support for @tsv (format array/scalar as TSV row)
2020 636 100 66     2577 if ($part eq '@tsv' || $part eq '@tsv()') {
2021 3         4 @next_results = map { JQ::Lite::Util::_apply_tsv($_) } @results;
  3         9  
2022 3         6 @$out_ref = @next_results;
2023 3         10 return 1;
2024             }
2025              
2026             # support for @base64 (format value as base64 string)
2027 633 100 66     1756 if ($part eq '@base64' || $part eq '@base64()') {
2028 6         10 @next_results = map { JQ::Lite::Util::_apply_base64($_) } @results;
  6         19  
2029 6         13 @$out_ref = @next_results;
2030 6         21 return 1;
2031             }
2032              
2033             # support for @base64d (decode base64-encoded string)
2034 627 100 66     1807 if ($part eq '@base64d' || $part eq '@base64d()') {
2035 7         12 @next_results = map { JQ::Lite::Util::_apply_base64d($_) } @results;
  7         26  
2036 3         6 @$out_ref = @next_results;
2037 3         10 return 1;
2038             }
2039              
2040             # support for @uri (percent-encode value)
2041 620 100 66     1634 if ($part eq '@uri' || $part eq '@uri()') {
2042 8         14 @next_results = map { JQ::Lite::Util::_apply_uri($_) } @results;
  8         27  
2043 8         13 @$out_ref = @next_results;
2044 8         29 return 1;
2045             }
2046              
2047             # support for split("separator")
2048 612 100       1142 if ($part =~ /^split\((.+)\)$/) {
2049 18         61 my $separator = JQ::Lite::Util::_parse_string_argument($1);
2050 18         31 @next_results = map { JQ::Lite::Util::_apply_split($_, $separator) } @results;
  18         44  
2051 18         32 @$out_ref = @next_results;
2052 18         70 return 1;
2053             }
2054              
2055             # support for substr(start[, length])
2056 594 100       1037 if ($part =~ /^substr(?:\((.*)\))?$/) {
2057 10 100       28 my $args_raw = defined $1 ? $1 : '';
2058 10         42 my @args = JQ::Lite::Util::_parse_arguments($args_raw);
2059 10         18 @next_results = map { JQ::Lite::Util::_apply_substr($_, @args) } @results;
  12         23  
2060 8         11 @$out_ref = @next_results;
2061 8         35 return 1;
2062             }
2063              
2064             # support for indices(value)
2065 584 100       1098 if ($part =~ /^indices\((.*)\)$/) {
2066 9         26 my @args = JQ::Lite::Util::_parse_arguments($1);
2067 9 50       16 my $needle = @args ? $args[0] : undef;
2068              
2069 9         15 @next_results = map { JQ::Lite::Util::_apply_indices($_, $needle) } @results;
  9         19  
2070 9         16 @$out_ref = @next_results;
2071 9         34 return 1;
2072             }
2073              
2074             # support for index(value)
2075 575 100       1089 if ($part =~ /^index\((.*)\)$/) {
2076 8         19 my @args = JQ::Lite::Util::_parse_arguments($1);
2077 8 50       14 my $needle = @args ? $args[0] : undef;
2078              
2079             @next_results = map {
2080 8 100 33     12 if (ref $_ eq 'ARRAY') {
  8 50       18  
2081 4         5 my $array = $_;
2082 4         4 my $found;
2083 4         9 for my $i (0 .. $#$array) {
2084 7 100       16 if (JQ::Lite::Util::_values_equal($array->[$i], $needle)) {
2085 3         10 $found = $i;
2086 3         4 last;
2087             }
2088             }
2089 4 100       13 defined $found ? $found : undef;
2090             }
2091             elsif (!ref $_ || ref($_) eq 'JSON::PP::Boolean') {
2092 4 100 100     13 if (!defined $_ || !defined $needle) {
2093 3         5 undef;
2094             }
2095             else {
2096 1         2 my $haystack = "$_";
2097 1         1 my $fragment = "$needle";
2098 1         2 my $pos = index($haystack, $fragment);
2099 1 50       4 $pos >= 0 ? $pos : undef;
2100             }
2101             }
2102             else {
2103 0         0 undef;
2104             }
2105             } @results;
2106              
2107 8         11 @$out_ref = @next_results;
2108 8         28 return 1;
2109             }
2110              
2111             # support for rindex(value)
2112 567 100       970 if ($part =~ /^rindex\((.*)\)$/) {
2113 9         27 my @args = JQ::Lite::Util::_parse_arguments($1);
2114 9 50       20 my $needle = @args ? $args[0] : undef;
2115              
2116             @next_results = map {
2117 9 100 33     15 if (ref $_ eq 'ARRAY') {
  9 50       22  
2118 4         5 my $array = $_;
2119 4         7 my $found;
2120 4         16 for (my $i = $#$array; $i >= 0; $i--) {
2121 8 100       19 if (JQ::Lite::Util::_values_equal($array->[$i], $needle)) {
2122 3         18 $found = $i;
2123 3         7 last;
2124             }
2125             }
2126 4 100       15 defined $found ? $found : undef;
2127             }
2128             elsif (!ref $_ || ref($_) eq 'JSON::PP::Boolean') {
2129 5 100 100     14 if (!defined $_ || !defined $needle) {
2130 3         7 undef;
2131             }
2132             else {
2133 2         3 my $haystack = "$_";
2134 2         3 my $fragment = "$needle";
2135 2         4 my $pos = rindex($haystack, $fragment);
2136 2 50       5 $pos >= 0 ? $pos : undef;
2137             }
2138             }
2139             else {
2140 0         0 undef;
2141             }
2142             } @results;
2143              
2144 9         16 @$out_ref = @next_results;
2145 9         41 return 1;
2146             }
2147              
2148             # support for paths()
2149 558 100 100     1590 if ($part eq 'paths()' || $part eq 'paths') {
2150 5         9 @next_results = ();
2151              
2152 5         12 for my $value (@results) {
2153 5         23 my $paths = JQ::Lite::Util::_apply_paths($value);
2154 5         14 push @next_results, @$paths;
2155             }
2156              
2157 5         9 @$out_ref = @next_results;
2158 5         19 return 1;
2159             }
2160              
2161             # support for paths(scalars)
2162 553 100       945 if ($part =~ /^paths\(\s*scalars\s*\)$/) {
2163 3         6 @next_results = ();
2164              
2165 3         7 for my $value (@results) {
2166 3         12 my $paths = JQ::Lite::Util::_apply_scalar_paths($value);
2167 3         39 push @next_results, @$paths;
2168             }
2169              
2170 3         6 @$out_ref = @next_results;
2171 3         13 return 1;
2172             }
2173              
2174             # support for leaf_paths()
2175 550 100 66     1509 if ($part eq 'leaf_paths()' || $part eq 'leaf_paths') {
2176 6         7 @next_results = map { JQ::Lite::Util::_apply_leaf_paths($_) } @results;
  6         17  
2177 6         9 @$out_ref = @next_results;
2178 6         19 return 1;
2179             }
2180              
2181             # support for getpath(path_expr)
2182 544 100       939 if ($part =~ /^getpath\((.*)\)$/) {
2183 14 50       48 my $path_expr = defined $1 ? $1 : '';
2184              
2185 14         24 @next_results = map { JQ::Lite::Util::_apply_getpath($self, $_, $path_expr) } @results;
  14         50  
2186 13         24 @$out_ref = @next_results;
2187 13         49 return 1;
2188             }
2189              
2190             # support for setpath(path_expr; value_expr)
2191 530 100       974 if ($part =~ /^setpath\((.*)\)$/) {
2192 10 50       40 my $args_raw = defined $1 ? $1 : '';
2193 10         34 my ($paths_expr, $value_expr) = JQ::Lite::Util::_split_semicolon_arguments($args_raw, 2);
2194              
2195 10         18 @next_results = map { JQ::Lite::Util::_apply_setpath($self, $_, $paths_expr, $value_expr) } @results;
  10         29  
2196 7         12 @$out_ref = @next_results;
2197 7         42 return 1;
2198             }
2199              
2200             # support for path()
2201 520 100       878 if ($part eq 'path') {
2202             @next_results = map {
2203 4 100       7 if (ref $_ eq 'HASH') {
  4 100       10  
2204 1         7 [ sort keys %$_ ]
2205             }
2206             elsif (ref $_ eq 'ARRAY') {
2207 1         4 [ 0..$#$_ ]
2208             }
2209             else {
2210 2         6 ''
2211             }
2212             } @results;
2213 4         7 @$out_ref = @next_results;
2214 4         13 return 1;
2215             }
2216              
2217             # support for is_empty
2218 516 100       862 if ($part eq 'is_empty') {
2219             @next_results = map {
2220 11 100 100     15 (ref $_ eq 'ARRAY' && !@$_) || (ref $_ eq 'HASH' && !%$_)
  11         59  
2221             ? JSON::PP::true
2222             : JSON::PP::false
2223             } @results;
2224 11         38 @$out_ref = @next_results;
2225 11         32 return 1;
2226             }
2227              
2228             # support for not (logical negation)
2229 505 100 66     1490 if ($part eq 'not' || $part eq 'not()') {
2230             @next_results = map {
2231 5 100       8 JQ::Lite::Util::_is_truthy($_) ? JSON::PP::false : JSON::PP::true
  5         13  
2232             } @results;
2233 5         34 @$out_ref = @next_results;
2234 5         15 return 1;
2235             }
2236              
2237             # support for jq's alternative operator: lhs // rhs
2238 500         803 my $coalesce_expr = $part;
2239 500 50       867 if (defined $coalesce_expr) {
2240 500         648 my $stripped = $coalesce_expr;
2241 500         1048 while ($stripped =~ /^\((.*)\)$/) {
2242 0         0 $stripped = $1;
2243 0         0 $stripped =~ s/^\s+|\s+$//g;
2244             }
2245              
2246 500 100       1216 if ($stripped =~ /^(.*?)\s*\/\/\s*(.+)$/) {
2247 5         18 my ($lhs_raw, $rhs_raw) = ($1, $2);
2248 5         9 my $lhs_expr = $lhs_raw;
2249 5         6 my $rhs_expr = $rhs_raw;
2250 5         25 $lhs_expr =~ s/^\s+|\s+$//g;
2251 5         11 $rhs_expr =~ s/^\s+|\s+$//g;
2252              
2253 5         8 @next_results = map { JQ::Lite::Util::_apply_coalesce($self, $_, $lhs_expr, $rhs_expr) } @results;
  7         25  
2254 5         40 @$out_ref = @next_results;
2255 5         23 return 1;
2256             }
2257             }
2258              
2259             # support for default(value)
2260 495 100       836 if ($part =~ /^default\((.+)\)$/) {
2261 6         17 my $default_value = $1;
2262 6         29 $default_value =~ s/^['"](.*?)['"]$/$1/;
2263              
2264 6 100       34 @results = @results ? @results : (undef);
2265              
2266             @next_results = map {
2267 6 100       19 defined($_) ? $_ : $default_value
  6         20  
2268             } @results;
2269 6         11 @$out_ref = @next_results;
2270 6         34 return 1;
2271             }
2272              
2273             # Fallback: value expressions (including literals like null/0/"text")
2274             {
2275 489         552 my $all_ok = 1;
  489         569  
2276 489         788 @next_results = ();
2277              
2278 489         766 for my $item (@results) {
2279 489         1272 my ($values, $ok) = JQ::Lite::Util::_evaluate_value_expression($self, $item, $part);
2280 489 100       1230 if ($ok) {
2281 13 50       35 if (@$values) {
2282 13         32 push @next_results, @$values;
2283             }
2284             else {
2285 0         0 push @next_results, undef;
2286             }
2287             }
2288             else {
2289 476         658 $all_ok = 0;
2290 476         888 last;
2291             }
2292             }
2293              
2294 489 100       1020 if ($all_ok) {
2295 13         23 @$out_ref = @next_results;
2296 13         45 return 1;
2297             }
2298             }
2299              
2300 476         1993 return 0;
2301             }
2302              
2303             1;