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