YATT::LRXML::EntityPath
Index
Code Index:
term ::= ( text | expr | pipeline ) ','? ;
pipeline ::= container? trail+ ;
trail ::= var | '[' term ']' ;
container::= '[' term* ']'
| '{' ( dot_name (':' text | '=' term )
| other+ ','?
)* '}' ;
var ::= (':'+ | '.'+) name ( '(' term* ')' )? ;
name ::= \w+ ;
dot_name ::= [\w\.]+ ;
expr ::= '=' text ;
text ::= word ( group word? )* ; -- group で始まるのは、container.
group ::= [\(\[\{] ( text | ',' )* [\}\]\)]
word ::= [\w\$\-\+\*/%<>] other* ;
other ::= [\w\$\-\+\*/%<>:\.!=] ;
# -*- mode: perl; coding: utf-8 -*-
package YATT::LRXML::EntityPath;
use strict;
use warnings FATAL => qw(all);
BEGIN {require Exporter; *import = \&Exporter::import}
our @EXPORT_OK = qw(parse_entpath is_nested_entpath);
our @EXPORT = @EXPORT_OK;
# is_nested_entpath($entpath, ?head?)
sub is_nested_entpath {
return unless defined $_[0] and ref $_[0] eq 'ARRAY';
my $item = shift;
return unless defined $item->[0] and ref $item->[0] eq 'ARRAY';
return 1 unless defined $_[0];
defined $item->[0][0] and $item->[0][0] eq $_[0];
}
our ($TRANS, $NODE, $ORIG);
sub mydie (@) {
my $fmt = shift;
my $diag = do {
if ($TRANS and $NODE) {
$TRANS->node_error($NODE, $fmt, @_);
} else {
sprintf $fmt, @_;
};
};
die $diag;
}
sub parse_entpath {
my ($pack, $orig, $trans, $node) = @_;
return undef unless defined $orig;
local $_ = $orig;
local $ORIG = $orig;
local $TRANS = $trans;
local $NODE = $node;
my @result;
if (wantarray) {
@result = &_parse_pipeline;
} else {
$result[0] = &_parse_pipeline;
}
if ($_ ne '') {
mydie "Unexpected token '$_' in entpath '$orig'";
}
wantarray ? @result : $result[0];
}
my %open_head = qw| ( call [ array { hash |;
my %open_rest = qw| ( call [ aref |;
my %close_ch = qw( ( ) [ ] { } );
my $re_var = qr{[:]+ (\w+) (\()?}x;
my $re_other= qr{[\w\$\-\+\*/%<>\.=\@\|!:]}x;
my $re_word = qr{[\w\$\-\+\*/%<>\.=\@\|!] $re_other*}x;
sub _parse_pipeline {
my @pipe;
if (s/^ \[ //x) {
# container
push @pipe, _parse_group(['array'], ']', \&_parse_term);
} elsif (s/^ \{ //x) {
push @pipe, &_parse_hash;
}
while (s/^$re_var | ^(\[) | ^(\{)//x) {
if ($2) {
# '('
push @pipe, _parse_group([call => $1], ')', \&_parse_term);
} elsif (defined $1) {
# \w+
push @pipe, [var => $1];
} elsif (defined $3) { # '['
push @pipe, _parse_group(['aref'], ']', \&_parse_term, 'expr');
} elsif (defined $4) {
push @pipe, _parse_group(['var'], '}', \&_parse_term);
} else {
mydie "?? $_";
}
}
wantarray ? @pipe : \@pipe;
}
my $re_grend = qr{ (?=[\)\]\}]) | $ }x;
my $re_text = qr{($re_word) # 1
(?: ([\(\[\{]) # 2
| $re_grend)?
| $re_grend
}x;
sub _parse_term {
my ($literal_type) = @_;
$literal_type ||= 'text';
# :foo() [call => foo]
# :foo(,) [call => foo => [text => '']]
# :foo(bar) [call => foo => [text => 'bar']]
# :foo(,,) [call => foo => [text => ''], [text => '']]
# :foo(bar,) [call => foo => [text => 'bar'], [text => '']]
# :foo(bar,,)[call => foo => [text => 'bar'], [text => '']]
if (s{^,}{}x) {
return [$literal_type => ''];
}
if (my $is_expr = s{^=}{}) {
return &_parse_expr;
}
my @result;
unless (s{^$re_text}{}) {
@result = &_parse_pipeline;
} else {
my $result = '';
TEXT: {
do {
$result .= $1 if defined $1;
$result .= $4 if defined $4;
if (my $opn = $2 || $3) {
# open group
$result .= $opn;
$result .= &_parse_group_string($close_ch{$opn});
} elsif (not defined $1 and not defined $4) {
last TEXT;
}
} while s{^(?: $re_text | ([\(\[\{]) | ([:\.]) ) }{}x;
}
@result = [$literal_type => $result];
}
s/^,//;
@result;
}
sub _parse_expr {
my $literal_type = 'expr';
if (s{^,}{}x) {
return [$literal_type => ''];
}
my $result = '';
TEXT:
while (s{^(?: $re_text | ([\(\[\{]) | ([:\.]) ) }{}x) {
$result .= $1 if defined $1;
$result .= $4 if defined $4;
if (my $opn = $2 || $3) {
# open group
$result .= $opn;
$result .= &_parse_group_string($close_ch{$opn});
} elsif (not defined $1 and not defined $4) {
last TEXT;
}
}
s/^,//;
[$literal_type => $result];
}
sub _parse_group {
my ($group, $close, $sub, @rest) = @_;
for (my ($len, $cnt) = length($_); $_ ne ''; $len = length($_), $cnt++) {
if (s/^ ([\)\]\}])//x) {
mydie "Paren mismatch: expect $close got $1 " if $1 ne $close;
last;
}
my @pipe = $sub->(@rest);
if ($cnt && $len == length($_)) {
mydie "Can't match: $_" . (defined $close ? " for $close" : "");
}
push @$group, @pipe <= 1 ? @pipe : \@pipe;
}
$group;
}
sub _parse_group_string {
my ($close) = @_;
my $result = '';
for (my ($len, $prev) = length($_); $_ ne ''
; $prev = $len, $len = length($_)) {
if (s/^ ([\)\]\}])//x) {
mydie "Paren mismatch: expect $close got $1 " if $1 ne $close;
$result .= $1;
last;
}
if (s/^($re_word | , )//x) {
$result .= $1;
} elsif (s/^([\(\[\{])//) {
$result .= $1;
$result .= &_parse_group_string($close_ch{$1});
}
if (defined $prev and $prev == length($_)) {
mydie "Can't parse entity_path group $ORIG (near $_)\n"
}
}
$result;
}
sub _parse_hash {
my @hash = ('hash');
for (my ($len, $cnt) = length($_); $_ ne ''; $len = length($_), $cnt++) {
if (s/^ ([\)\]\}])//x) {
mydie "Paren mismatch: expect \} got $1 " if $1 ne '}';
last;
}
# {!=,:var} ã許ãã«ã¯â¦
if (s/^([\w\.\-]+) [:=] //x || s/^($re_other+) ,?//x) {
# â array ã§ã許ãã¹ãã?
my $str = $1;
push @hash, [$str =~ s/^:// ? 'var' : 'text', $str];
}
my @value = &_parse_term;
push @hash, @value > 1 ? \@value : $value[0];
unless (length($_) < $len) {
mydie "Infinite loop on parse_hash: $_";
}
}
# XXX: Give more detailed diag!
mydie "Odd number of hash elements" if (@hash - 1) % 2;
\@hash;
}
1;