package JQ::Lite::Parser;
use strict;
use warnings;
use JQ::Lite::Util ();
sub parse_query {
my ($query) = @_;
return () unless defined $query;
return () if $query =~ /^\s*\.\s*$/;
my @parts = JQ::Lite::Util::_split_top_level_pipes($query);
@parts = map {
my $part = $_;
$part =~ s/^\s+|\s+$//g;
$part;
} @parts;
@parts = map {
if ($_ eq '.[]') {
'flatten';
}
elsif ($_ =~ /^\.(.+)$/) {
my $rest = $1;
if ($rest =~ /^\s*[+\-*\/%]/ || $rest =~ /[+\-*\/%]/ || $rest =~ /\b(?:floor|ceil|round|tonumber)\b/) {
$_;
}
else {
$rest;
}
}
else {
$_;
}
} @parts;
@parts = map { _lower_object_shorthand($_) } @parts;
my @expanded;
for my $part (@parts) {
next unless defined $part;
my $trimmed = $part;
$trimmed =~ s/^\s+|\s+$//g;
if ($trimmed =~ /^\(.*\)$/s) {
my $inner = JQ::Lite::Util::_strip_wrapping_parens($trimmed);
if (defined $inner && length $inner && $inner ne $trimmed) {
my @inner_parts = parse_query($inner);
if (@inner_parts) {
push @expanded, @inner_parts;
next;
}
}
}
push @expanded, $trimmed;
}
return @expanded;
}
sub _lower_object_shorthand {
my ($text) = @_;
return $text unless defined $text;
return $text if index($text, '{') == -1;
my $result = '';
my $len = length $text;
my $i = 0;
my $string;
my $escape = 0;
while ($i < $len) {
my $char = substr($text, $i, 1);
if (defined $string) {
$result .= $char;
if ($escape) {
$escape = 0;
}
elsif ($char eq '\\') {
$escape = 1;
}
elsif ($char eq $string) {
undef $string;
}
$i++;
next;
}
if ($char eq "'" || $char eq '"') {
$string = $char;
$result .= $char;
$i++;
next;
}
if ($char eq '{') {
my ($body, $consumed) = _extract_object_body($text, $i);
if (defined $body) {
my $lowered = _lower_object_constructor($body);
$result .= '{' . $lowered . '}';
$i += $consumed;
next;
}
}
$result .= $char;
$i++;
}
return $result;
}
sub _extract_object_body {
my ($text, $start) = @_;
my $len = length $text;
my $depth = 0;
my $string;
my $escape = 0;
for (my $i = $start; $i < $len; $i++) {
my $char = substr($text, $i, 1);
if (defined $string) {
if ($escape) {
$escape = 0;
next;
}
if ($char eq '\\') {
$escape = 1;
next;
}
if ($char eq $string) {
undef $string;
}
next;
}
if ($char eq "'" || $char eq '"') {
$string = $char;
next;
}
if ($char eq '{') {
$depth++;
next;
}
if ($char eq '}') {
$depth--;
if ($depth == 0) {
my $body = substr($text, $start + 1, $i - $start - 1);
return ($body, $i - $start + 1);
}
next;
}
}
return (undef, 1);
}
sub _lower_object_constructor {
my ($inner) = @_;
return $inner unless defined $inner;
my @parts = JQ::Lite::Util::_split_top_level_commas($inner);
return $inner unless @parts;
my @transformed;
for my $part (@parts) {
next unless defined $part;
my $trimmed = $part;
$trimmed =~ s/^\s+|\s+$//g;
next if $trimmed eq '';
my ($lhs, $rhs) = JQ::Lite::Util::_split_top_level_colon($part);
if (defined $lhs && defined $rhs) {
my $key = $lhs;
$key =~ s/^\s+|\s+$//g;
my $value = _lower_object_shorthand($rhs);
$value =~ s/^\s+|\s+$//g;
push @transformed, "$key: $value";
next;
}
if (!defined $lhs && $trimmed =~ /^[A-Za-z_][A-Za-z0-9_]*$/) {
push @transformed, "$trimmed: .$trimmed";
next;
}
if (defined $lhs && !defined $rhs) {
my $key = $lhs;
$key =~ s/^\s+|\s+$//g;
next if $key eq '';
push @transformed, "$key: .$key";
next;
}
my $lowered = _lower_object_shorthand($trimmed);
$lowered =~ s/^\s+|\s+$//g;
push @transformed, $lowered if length $lowered;
}
return join(', ', @transformed);
}
1;