use
warnings
qw(FATAL all NONFATAL misc)
;
use
5.010;
no
if
$] >= 5.017011,
warnings
=>
"experimental"
;
re_body
re_entopn
re_att
re_name
re_evar ch_etext
re_eparen
re_eopen re_eclose
template
chunklist
startln endln
startpos curpos
cf_namespace
cf_vfs
cf_default_part
cf_base cf_scheme cf_path cf_encoding cf_debug
cf_all
cf_special_entities
cf_body_argument
cf_body_argument_type
subroutes
rootroute
cf_match_argsroute_first
_original_entpath
/
;
sub
default_public_part {
'page'
}
sub
default_private_part {
'widget'
}
sub
default_body_argument {
'body'
}
sub
default_part_for {
(
my
MY
$self
,
my
Template
$tmpl
) =
@_
;
$tmpl
->{cf_public}
?
$self
->default_public_part
:
$self
->default_private_part;
}
sub
after_new {
my
MY
$self
=
shift
;
$self
->SUPER::after_new;
Scalar::Util::weaken(
$self
->{cf_vfs})
if
$self
->{cf_vfs};
$self
->{cf_body_argument} //=
$self
->default_body_argument;
$self
->{cf_namespace} ||= [
qw(yatt perl)
];
my
$nspat
=
qr!@{[join "|", $self->namespace]}!
;
$self
->{re_name} ||=
$self
->re_name;
$self
->{re_decl} ||=
qr{<!(?:(?<declname>$nspat(?::\w++)+)
|(?:--\#(?<comment>$nspat(?::\w++)*)))\b}
xs;
my
$entOpen
=
do
{
my
$entbase
=
qq{(?<entity>$nspat)}
;
$entbase
.=
sprintf
(
q{(?=%s)}
,
join
"|"
,
':'
,
sprintf
(
q{(?<lcmsg>%s)}
,
join
"|"
,
q{(?<msgopn>(?:\#\w+)?\[{2,}
)}
,
q{(?<msgsep>\|{2,}
)}
,
q{(?<msgclo>\]{2,}
)}));
my
@entPat
=
$entbase
;
push
@entPat
,
sprintf
q{(?<special>(?:%s))\(}
,
join
"|"
, lexpand(
$self
->{cf_special_entities})
if
$self
->{cf_special_entities};
sprintf
q{&(?:%s)}
,
join
"|"
,
@entPat
;
};
$self
->{re_att}
||=
qr{(?<ws>\s++)
| (?<comment>--+.*?--+)
| (?<macro>%(?:[\w\:\.]+(?:[\w:\.\-=\[\]\{\}
\(,\)]+)?);)
|
(?:
'(?<sq>[^'
]*+)'
|
"(?<dq>[^\"]*+)"
|(?<nest>\[) | (?<nestclo>\])
|
$entOpen
|(?<bare>[^\s'\"<>\[\]/=;]++)
)
(?<equal>\s*=\s*+)?+
}xs;
$self
->{re_body} ||=
qr{$entOpen
|<(?:(?<clo>/?)(?<opt>:?)(?<elem>$nspat(?::\w++)+)
|\?(?<pi>$nspat(?::\w++)*))\b
}
xs;
$self
->{re_entopn} =
qr{$entOpen}
xs;
$self
->{re_eopen} ||=
qr{(?<open> [\(\{\[])}
xs;
$self
->{re_eclose} ||=
qr{(?<close> [\)\}
\]])}xs;
$self
->{re_evar} ||=
qr{: (?<var>\w+)}
xs;
$self
->{ch_etext} ||=
qr{(?: [^\ \t\n,;:()\[\]{}
])}xs;
$self
->{re_eparen} ||=
qr{(\( (?<paren> (?: (?> [^()]+) | (?-2) )*) \) )}
xs;
$self
;
}
([
EntMatch
=>
fields
=> [
qw/
entity
lcmsg
msgopn msgsep msgclo
special
/
]
, [
AttMatch
=>
fields
=> [
qw/ws comment
macro
sq dq bare
nest nestclo
equal
/
]]
]
);
sub
load_from {
my
(
$pack
,
$loadSpec
,
$tmplSpec
,
@moreLoadArgs
) =
@_
;
my
(
$loadType
,
@loadArgs
) =
ref
$loadSpec
?
@$loadSpec
:
$loadSpec
;
unless
(
defined
$loadType
) {
croak
"Undefined source type"
;
}
my
$sub
=
$pack
->can(
"load_${loadType}_into"
)
or croak
"Unknown source type: $loadType"
;
my
(
$tmplFrom
,
@tmplArgs
) =
ref
$tmplSpec
?
@$tmplSpec
:
$tmplSpec
;
my
Template
$tmpl
=
$pack
->Template->new(
@tmplArgs
);
$sub
->(
$pack
,
$tmpl
,
$tmplFrom
,
all
=> 1,
@loadArgs
,
@moreLoadArgs
);
}
sub
load_file_into {
my
(
$pack
,
$tmpl
,
$fn
) =
splice
@_
, 0, 3;
croak "Template argument is missing!
YATT::Lite::Parser->from_file(filename, templateObject)"
unless
defined
$tmpl
and UNIVERSAL::isa(
$tmpl
,
$pack
->Template);
unless
(
defined
$fn
) {
croak
"filename is undef!"
;
}
my
MY
$self
=
ref
$pack
?
$pack
->configure(
@_
) :
$pack
->new(
@_
);
open
my
$fh
,
'<'
,
$fn
or
die
"Can't open $fn: $!"
;
binmode
$fh
,
":encoding($$self{cf_encoding})"
if
$$self
{cf_encoding};
$self
->{cf_path} =
$fn
;
$self
->{cf_scheme} =
'file'
;
my
$string
=
do
{
local
$/;
untaint_unless_tainted(
$fn
,
scalar
<
$fh
>);
};
$self
->load_string_into(
$tmpl
,
$string
);
}
sub
load_string_into {
(
my
$pack
,
my
Template
$tmpl
) =
splice
@_
, 0, 2;
$tmpl
->
reset
;
my
MY
$self
=
ref
$pack
?
$pack
->configure(
@_
[1 ..
$#_
])
:
$pack
->new(
@_
[1 ..
$#_
]);
unless
(
defined
$_
[0]) {
croak
"template string is undef!"
;
}
$self
->parse_decl(
$tmpl
,
$_
[0]);
$self
->parse_body(
$tmpl
)
if
$self
->{cf_all};
wantarray
? (
$tmpl
,
$self
) :
$tmpl
;
}
sub
parse_body {
(
my
MY
$self
,
my
Template
$tmpl
) =
@_
;
return
if
$tmpl
->{parse_ok};
$self
->{template} =
$tmpl
;
$self
->parse_widget(
$_
)
for
$tmpl
->list_parts(
$self
->Widget);
$tmpl
->{parse_ok} = 1;
}
sub
parse_decllist_entities {
(
my
MY
$self
,
my
Template
$tmpl
) =
@_
;
foreach
my
Part
$part
(
$tmpl
->list_parts) {
my
$decllist
=
$part
->{decllist} or
next
;
foreach
my
$node
(
@$decllist
) {
$node
->[NODE_TYPE] == TYPE_ATT_TEXT
or
next
;
$self
->{endln} =
$node
->[NODE_LNO];
my
(
$type
,
$dflag
,
$default
)
=
$self
->parse_type_dflag_default(
$node
->[NODE_BODY]);
if
(
ref
$node
->[NODE_PATH]) {
...
}
$node
->[NODE_BODY] = [
$type
,
$dflag
,
(
defined
$default
? lexpand(
$self
->_parse_text_entities_at(
$node
->[NODE_BODY_BEGIN],
$default
))
: ())];
}
}
}
sub
posinfo {
(
my
MY
$self
) =
shift
;
(
$self
->{startpos},
$self
->{curpos});
}
sub
add_posinfo {
(
my
MY
$self
,
my
(
$len
,
$sync
)) =
@_
;
$self
->{curpos} +=
$len
;
$self
->{startpos} =
$self
->{curpos}
if
$sync
;
$len
;
}
sub
update_posinfo {
my
MY
$self
=
shift
;
my
(
$sync
) =
splice
@_
, 1;
$self
->{startpos} =
$self
->{curpos}
if
$sync
;
}
sub
ensure_default_part {
(
my
MY
$self
,
my
Template
$tmpl
) =
@_
;
my
Part
$part
=
$self
->build(
$self
->primary_ns
,
args
=>
$self
->default_part_for(
$tmpl
)
,
''
,
implicit
=> 1
,
startpos
=>
$self
->{startpos},
bodypos
=>
$self
->{startpos}
);
$self
->add_part(
$tmpl
,
$part
);
$part
;
}
sub
parse_decl {
(
my
MY
$self
,
my
Template
$tmpl
,
my
$str
,
my
@config
) =
@_
;
break_parser();
$self
->{template} =
$tmpl
;
$self
->configure(
@config
);
$tmpl
->{cf_string} =
$str
;
$tmpl
->{cf_utf8} = Encode::is_utf8(
$str
);
$self
->{startln} =
$self
->{endln} = 1;
(
$self
->{startpos},
$self
->{curpos},
my
$total
) = (0, 0,
length
$str
);
my
Part
$part
;
while
(
$str
=~ s{^(.*?)(
$$self
{re_decl})}{}s) {
if
(not
$part
and (
length
$1 || $+{comment})) {
$part
=
$self
->ensure_default_part(
$tmpl
);
}
$self
->add_text(
$part
, $1)
if
length
$1;
$self
->{curpos} =
$total
-
length
$str
;
if
(
my
$comment_ns
= $+{comment}) {
unless
(
$str
=~ s{^(.*?)-->(\r?\n)?}{}s) {
die
$self
->synerror_at(
$self
->{startln},
q{Comment is not closed}
);
}
my
$nlines
= numLines($1) + ($2 ? 1 : 0);
$self
->{curpos} +=
length
$&;
push
@{
$part
->{toks}},
do
{
my
$node
= [];
$node
->[NODE_TYPE] = TYPE_COMMENT;
@{
$node
}[NODE_BEGIN, NODE_END] =
$self
->posinfo(
$str
);
$node
->[NODE_LNO] =
$self
->{startln};
$node
->[NODE_PATH] =
$comment_ns
;
$node
->[NODE_BODY] =
$nlines
;
$node
->[NODE_ATTLIST] = $1;
$node
;
};
$self
->{startln} =
$self
->{endln} +=
$nlines
;
next
;
}
my
$declkind
= $+{declname};
my
(
$ns
,
$kind
) =
split
/:/,
$declkind
, 2;
if
(
my
$sub
=
$self
->can(
"declare_$kind"
)) {
my
@args
=
$self
->parse_attlist(\
$str
, 1);
$part
=
$sub
->(
$self
,
$tmpl
,
$ns
,
@args
)
//
$part
;
if
(
$part
) {
$part
->{decllist} = \
@args
;
}
}
elsif
(
$self
->can(
"build_$kind"
)) {
my
(
@args
) =
$self
->parse_attlist(\
$str
, 1);
my
$saved_attlist
= [
@args
];
my
(
$partName
,
$mapping
) =
$self
->cut_partname_and_route(
$declkind
, \
@args
);
$self
->add_part(
$tmpl
,
$part
=
$self
->build(
$ns
,
$kind
,
$kind
,
$partName
));
$part
->{decllist} =
$saved_attlist
;
if
(
$mapping
) {
$self
->add_route(
$part
,
$mapping
);
}
$self
->add_args(
$part
,
@args
);
}
else
{
die
$self
->synerror_at(
$self
->{startln},
q{Unknown declarator (<!%s:%s >)}
,
$ns
,
$kind
);
}
unless
(
$str
=~ s{^>([\ \t]*\r?\n)?}{}s) {
die
$self
->synerror_at(
$self
->{startln},
q{Declarator '<!%s:%s' is not closed with '>': %s}
,
$ns
,
$kind
,
$str
);
}
unless
($1) {
die
$self
->synerror_at(
$self
->{startln},
q{<!%s:%s> must end with newline!}
,
$ns
,
$kind
);
}
$self
->add_posinfo(
length
$&);
$self
->{endln} += numLines($1);
if
(
$part
) {
$part
->{cf_bodypos} =
$self
->{curpos};
$part
->{cf_bodyln} =
$self
->{endln};
}
}
continue
{
$self
->{startpos} =
$self
->{curpos};
}
$part
//=
$self
->ensure_default_part(
$tmpl
);
push
@{
$part
->{toks}}, nonmatched(
$str
);
$part
->{cf_endln} =
$self
->{endln} += numLines(
$str
);
$self
->finalize_template(
$tmpl
);
}
sub
cut_partname_and_route {
(
my
MY
$self
,
my
(
$declkind
,
$argList
)) =
@_
;
my
$nameAtt
= YATT::Lite::Constants::cut_first_att(
$argList
) or
do
{
my
Template
$tmpl
=
$self
->{template};
die
$self
->synerror_at(
$self
->{startln},
q{No part name in %s\n%s}
,
$declkind
, nonmatched(
$tmpl
->{cf_string}));
};
my
(
$partName
,
$mapping
);
if
(
$nameAtt
->[NODE_TYPE] == TYPE_ATT_NAMEONLY) {
$partName
=
$nameAtt
->[NODE_PATH];
}
elsif
(
$nameAtt
->[NODE_TYPE] == TYPE_ATT_TEXT) {
if
(
ref
$nameAtt
->[NODE_BODY]) {
my
$t
=
$YATT::Lite::Constants::TYPE_
[
$nameAtt
->[NODE_BODY][0][NODE_TYPE]];
die
$self
->synerror_at(
$self
->{startln}
,
q{%s got wrong token for route spec: %s}
,
$declkind
,
$t
);
}
if
(
$nameAtt
->[NODE_BODY] eq
''
) {
$partName
=
$nameAtt
->[NODE_PATH] //
''
;
}
else
{
$mapping
=
$self
->parse_location
(
$nameAtt
->[NODE_BODY],
$nameAtt
->[NODE_PATH]) or
do
{
die
$self
->synerror_at(
$self
->{startln}
,
q{Invalid location in %s - "%s"}
,
$declkind
,
$nameAtt
->[NODE_BODY])
};
$partName
=
$nameAtt
->[NODE_PATH]
//
$self
->location2name(
$nameAtt
->[NODE_BODY]);
}
}
else
{
die
$self
->synerror_at(
$self
->{startln},
q{Invalid part name in %s}
,
$declkind
);
}
(
$partName
,
$mapping
);
}
sub
finalize_template {
(
my
MY
$self
,
my
Template
$tmpl
) =
@_
;
$self
->fixup_template_foreach_part_posinfo(
$tmpl
);
$tmpl
->{cf_nlines} =
$self
->{endln};
if
(
$self
->{cf_match_argsroute_first}) {
if
(
$self
->{rootroute}) {
$self
->subroutes->append(
$self
->{rootroute});
}
}
if
(
$self
->{subroutes}) {
$tmpl
->{cf_subroutes} =
$self
->{subroutes};
}
$tmpl
}
sub
fixup_template_foreach_part_posinfo {
(
my
MY
$self
,
my
Template
$tmpl
) =
@_
;
my
Part
$prev
;
foreach
my
Part
$part
(@{
$tmpl
->{partlist}}) {
if
(
$prev
) {
unless
(
defined
$part
->{cf_startpos}) {
die
$self
->synerror_at(
$self
->{startln},
q{startpos is undef}
);
}
unless
(
defined
$prev
->{cf_bodypos}) {
die
$self
->synerror_at(
$self
->{startln},
q{prev bodypos is undef}
);
}
$prev
->{cf_bodylen} =
$part
->{cf_startpos} -
$prev
->{cf_bodypos};
}
if
(
$part
->{toks} and @{
$part
->{toks}}) {
if
(
$part
->{toks}[-1] =~ s/(?:\r?\n)+\Z//) {
push
@{
$part
->{toks}},
"\n"
unless
$tmpl
->{cf_ignore_trailing_newlines};
}
}
if
(
my
$sub
=
$part
->can(
'fixup'
)) {
$sub
->(
$part
,
$tmpl
,
$self
);
}
}
continue
{
$prev
=
$part
}
if
(
$prev
) {
$prev
->{cf_bodylen} =
length
(
$tmpl
->{cf_string}) -
$prev
->{cf_bodypos};
}
}
sub
parse_attlist {
(
my
MY
$self
,
my
(
$strref
,
@opt
)) =
@_
;
$self
->parse_attlist_with_lvalue(
$self
->{curpos},
undef
,
$strref
,
@opt
);
}
sub
parse_attlist_with_lvalue {
(
my
MY
$self
,
my
(
$outer_start
,
$outer_lvalue
,
$strref
,
@opt
)) =
@_
;
my
(
$for_decl
) =
@opt
;
my
(
@result
,
@lvalue
);
my
$curln
=
$self
->{endln};
while
(
$$strref
=~ s{^
$$self
{re_att}}{}xs) {
my
$start
=
$self
->{curpos};
$self
->{curpos} +=
length
$&;
$self
->{endln} += numLines($&);
my
AttMatch
$m
= \%+;
next
if
$m
->{ws} ||
$m
->{comment};
next
if
$m
->{macro};
my
@common
= (
$start
,
$self
->{curpos},
$curln
);
my
$mklval
=
sub
{
if
(
@lvalue
) {
my
(
$s
,
$p
,
$l
,
$n
) =
splice
(
@lvalue
);
(
$s
,
$self
->{curpos},
$l
,
$n
);
}
else
{
(
@common
,
undef
);
}
};
if
(not
$m
->{equal}) {
if
(
$m
->{nestclo}) {
unless
(
$outer_lvalue
) {
Carp::croak(
"syntax error"
);
}
my
(
$s
,
$p
,
$l
,
$n
) =
do
{
if
(
$outer_lvalue
&&
@$outer_lvalue
) {
splice
(
@$outer_lvalue
);
}
else
{
(
@common
,
undef
)
}
};
my
$node
= [];
$node
->[NODE_TYPE] = TYPE_ATT_NESTED;
$node
->[NODE_BEGIN] =
$outer_start
;
$node
->[NODE_END] =
$self
->{curpos};
$node
->[NODE_LNO] =
$l
;
$node
->[NODE_PATH] =
$n
;
$node
->[NODE_BODY] = \
@result
;
return
$node
;
}
if
(
$m
->{nest}) {
push
@result
,
$self
->parse_attlist_with_lvalue(
$start
, \
@lvalue
,
$strref
,
@opt
);
}
else
{
push
@result
,
my
$node
= [];
{
if
(
$m
->{bare} and is_ident(
$m
->{bare})) {
if
(
@lvalue
) {
$node
->[NODE_TYPE] = TYPE_ATT_BARENAME;
@{
$node
}[NODE_BEGIN, NODE_END, NODE_LNO, NODE_PATH] =
splice
(
@lvalue
);
$node
->[NODE_BODY] =
$m
->{bare};
}
else
{
$node
->[NODE_TYPE] = TYPE_ATT_NAMEONLY;
@{
$node
}[NODE_BEGIN, NODE_END, NODE_LNO] =
@common
;
$node
->[NODE_PATH] = split_ns(
$m
->{bare});
}
}
elsif
($+{entity} or $+{special}) {
if
(
$m
->{lcmsg}) {
die
$self
->synerror_at(
$self
->{startln}
,
q{l10n msg is not allowed here}
);
}
$node
->[NODE_TYPE] = TYPE_ATT_TEXT;
@{
$node
}[NODE_BEGIN, NODE_END, NODE_LNO, NODE_PATH] =
$mklval
->();
$_
=
$$strref
;
$node
->[NODE_BODY] = [
$self
->mkentity(
@common
)];
$node
->[NODE_END] =
$self
->{curpos};
}
else
{
my
(
$quote
,
$value
) = oneof(
$m
,
qw(bare sq dq)
);
$node
->[NODE_TYPE] = TYPE_ATT_TEXT;
@{
$node
}[NODE_BEGIN, NODE_END, NODE_LNO, NODE_PATH] =
$mklval
->();
$node
->[NODE_BODY_BEGIN] =
$start
+ (
$quote
? 1 : 0);
splice
@$node
, NODE_BODY, 0, (
$for_decl
?
$value
:
$self
->_parse_text_entities_at(
$node
->[NODE_BODY_BEGIN],
$value
)
);
}
};
$node
->[NODE_BODY_END] =
$self
->{curpos};
}
}
elsif
(
not
@lvalue
) {
if
(
$m
->{bare} and is_ident(
$m
->{bare})) {
@lvalue
= (
@common
, split_ns(
$m
->{bare}));
}
elsif
(
$m
->{nestclo}) {
my
(
$s
,
$p
,
$l
) =
@common
;
@lvalue
= (
$outer_start
,
undef
,
$l
, [
splice
@result
]);
}
else
{
Carp::croak(
"unknown"
);
}
}
else
{
die
$self
->synerror_at(
$self
->{startln}
,
q{assignment (=) after assignment (=) is not allowed}
);
}
}
continue
{
$curln
=
$self
->{endln};
$self
->_verify_token(
$self
->{curpos},
$$strref
)
if
$self
->{cf_debug};
}
wantarray
?
@result
: \
@result
;
}
sub
mkentity {
(
my
MY
$self
) =
shift
;
my
$node
= [];
$node
->[NODE_TYPE] = TYPE_ENTITY;
@{
$node
}[NODE_BEGIN, NODE_END, NODE_LNO] =
@_
;
if
(
my
$ns
= $+{entity}) {
$node
->[NODE_PATH] =
$ns
;
splice
@$node
, NODE_BODY, 0,
$self
->_parse_entpath;
}
elsif
(
my
$special
= $+{special}) {
$node
->[NODE_BODY] = [
call
=>
$special
,
$self
->_parse_entpath(
_parse_entgroup
=>
')'
)];
}
else
{
die
"mkentity called without entity or special"
;
}
$node
->[NODE_END] =
$self
->{curpos};
$node
;
}
sub
split_ns {
defined
(
my
$value
=
shift
)
or
return
undef
;
local
%+;
my
@names
=
split
/:/,
$value
;
@names
> 1 ? \
@names
:
$value
;
}
sub
parse_widget {
(
my
MY
$self
,
my
Widget
$widget
) =
@_
;
$self
->{startln} =
$self
->{endln} =
$widget
->{cf_bodyln};
local
$self
->{chunklist} =
my
$chunks
= [@{
$widget
->{toks} //= []}];
local
$_
=
@$chunks
&& !
ref
$chunks
->[0] ?
shift
@$chunks
:
''
;
$self
->{startpos} =
$self
->{curpos} =
$widget
->{cf_bodypos};
$self
->_parse_body(
$widget
,
$widget
->{tree} = []);
push
@{
$widget
->{tree}}, nonmatched(
$_
);
$widget
;
}
sub
_get_chunk {
(
my
MY
$self
,
my
$sink
) =
@_
;
my
$chunks
=
$self
->{chunklist};
if
(
length
$_
) {
push
@$sink
,
$_
if
$sink
;
$self
->{startln} =
$self
->{endln} += numLines(
$_
);
$self
->{curpos} =
$self
->{startpos} +=
length
$_
;
$_
=
''
;
}
while
(
@$chunks
and
ref
$chunks
->[0]) {
my
$next
=
shift
@$chunks
;
push
@$sink
,
$next
if
$sink
;
$self
->{startln} =
$self
->{endln} +=
$next
->[NODE_BODY];
$self
->{curpos} =
$self
->{startpos} =
$next
->[NODE_END];
}
return
unless
@$chunks
;
$_
=
shift
@$chunks
;
1
}
sub
nonspace {
local
(%+, $&, $1, $2);
$_
[0] =~ /\S/;
}
sub
splitline {
local
(%+, $&, $1, $2);
split
/(?<=\n)/,
$_
[0];
}
sub
_verify_token {
(
my
MY
$self
,
my
$pos
) =
splice
@_
, 0, 2;
unless
(
defined
$pos
) {
die
$self
->synerror_at(
$self
->{startln},
q{Token pos is undef!: now='%s'}
,
$_
[0]);
}
my
$tok
=
$self
->{template}->source_substr(
$pos
,
length
$_
[0]);
unless
(
defined
$tok
) {
die
$self
->synerror_at(
$self
->{startln},
q{Token substr is empty!: now='%s'}
,
$_
[0]);
}
unless
(
$tok
eq
$_
[0]) {
die
$self
->synerror_at(
$self
->{startln},
q{Token mismatch!: substr='%s', now='%s'}
,
$tok
,
$_
[0]);
}
}
sub
drop_leading_ws {
my
$list
=
shift
;
local
(%+, $1, $2, $&);
pop
@$list
while
@$list
and
$list
->[-1] =~ /^\s*$/s;
}
sub
build {
(
my
MY
$self
,
my
(
$ns
,
$decl
,
$kind
,
$partName
,
@rest
)) =
@_
;
local
%+;
$self
->can(
"build_$kind"
)->
(
$self
,
name
=>
$partName
,
decl
=>
$decl
,
kind
=>
$kind
,
namespace
=>
$ns
,
folder
=>
$self
->{template}
,
startpos
=>
$self
->{startpos},
@rest
);
}
sub
build_widget {
shift
->Widget->new(
@_
) }
sub
build_page {
shift
->Page->new(
@_
) }
sub
build_action {
shift
->Action->new(
@_
) }
sub
build_data {
shift
->Data->new(
@_
) }
sub
build_entity {
shift
->Entity->new(
@_
) }
sub
declare_base {
(
my
MY
$self
,
my
Template
$tmpl
,
my
(
$ns
,
@args
)) =
@_
;
$self
->{cf_vfs}->declare_base(
$self
,
$tmpl
,
$ns
,
@args
)
if
@args
;
undef
;
}
sub
declare_args {
(
my
MY
$self
,
my
Template
$tmpl
,
my
(
$ns
,
@args
)) =
@_
;
my
$kind
=
'args'
;
my
$declkind
=
join
(
":"
,
$ns
,
$kind
);
my
Widget
$newpart
=
$self
->cut_implicit_default_part(
$tmpl
,
$declkind
)
||
$self
->build(
$ns
,
$kind
=>
$self
->default_part_for(
$tmpl
),
''
,
startln
=>
$self
->{startln});
if
(not
grep
{/\S/} @{
$newpart
->{toks}}) {
$newpart
->configure(
startln
=>
$self
->{startln},
);
$newpart
->{toks} = [];
}
$self
->cut_root_route_and_install_url_params(
$newpart
, \
@args
);
$self
->add_part(
$tmpl
,
$newpart
, 1);
$self
->add_args(
$newpart
,
@args
);
$newpart
;
}
sub
cut_implicit_default_part {
(
my
MY
$self
,
my
Template
$tmpl
,
my
(
$declkind
)) =
@_
;
(
my
Part
$oldpart
,
my
@other
) =
$self
->list_default_parts(
$tmpl
);
unless
(not
$oldpart
or
$oldpart
->{cf_implicit}) {
die
$self
->synerror_at(
$self
->{startln}
,
q{<!%s> at line %d conflicts with <!%s>}
,
$oldpart
->syntax_keyword,
$oldpart
->{cf_startln}
,
$declkind
);
}
if
(
$oldpart
and
$tmpl
->{partlist} and @{
$tmpl
->{partlist}} == 1
and
$tmpl
->{partlist}[0] ==
$oldpart
) {
shift
@{
$tmpl
->{partlist}};
}
else
{
$oldpart
->{cf_suppressed} = 1
if
$oldpart
;
return
undef
;
}
}
sub
cut_root_route_and_install_url_params {
(
my
MY
$self
,
my
Part
$part
,
my
(
$argList
)) =
@_
;
return
unless
@$argList
and
$argList
->[0]
and
$argList
->[0][NODE_TYPE] == TYPE_ATT_TEXT
and not
defined
$argList
->[0]->[NODE_PATH];
my
$patNode
=
shift
@$argList
;
if
(
ref
$patNode
->[NODE_BODY]) {
my
$t
=
$YATT::Lite::Constants::TYPE_
[
$patNode
->[NODE_BODY][0][NODE_TYPE]];
die
$self
->synerror_at(
$self
->{startln}
,
q{%s got wrong token for route spec: %s}
,
$part
->syntax_keyword,
$t
);
}
my
$mapping
=
$self
->parse_location(
$patNode
->[NODE_BODY],
''
,
$part
)
or
do
{
die
$self
->synerror_at(
$self
->{startln}
,
q{Invalid route spec in %s - "%s"}
,
$part
->syntax_keyword,
$patNode
->[NODE_BODY]);
};
if
(
$self
->{cf_match_argsroute_first}) {
$self
->{rootroute} =
$mapping
;
}
else
{
$self
->{subroutes}->append(
$mapping
);
}
$self
->add_url_params(
$part
, lexpand(
$mapping
->cget(
'params'
)));
}
sub
declare_action {
(
my
MY
$self
,
my
Template
$tmpl
,
my
(
$ns
,
@args
)) =
@_
;
my
$kind
=
'action'
;
my
$declkind
=
join
(
":"
,
$ns
,
$kind
);
my
(
$partName
,
$mapping
) =
$self
->cut_partname_and_route(
$declkind
, \
@args
);
if
(
$partName
eq
''
and not
$mapping
) {
my
$declname
=
"$declkind ''"
;
if
(
my
Part
$implicit
=
$self
->cut_implicit_default_part(
$tmpl
,
$declname
)) {
die
$self
->synerror_at(
$self
->{startln}
,
q{<!%s> conflicts with name-less default widget}
,
"$declkind ''"
);
}
}
my
Part
$newpart
=
$self
->build(
$ns
,
$kind
=>
$kind
,
$partName
,
startln
=>
$self
->{startln});
$self
->add_part(
$tmpl
,
$newpart
, 1);
if
(
$mapping
) {
$self
->add_route(
$newpart
,
$mapping
);
}
$self
->add_args(
$newpart
,
@args
);
$newpart
;
}
sub
list_default_parts {
(
my
MY
$self
,
my
Template
$tmpl
) =
@_
;
return
unless
$tmpl
->{partlist};
grep
{
my
Part
$part
=
$_
;
$part
->{cf_name} eq
''
and not
$part
->{cf_suppressed};
} @{
$tmpl
->{partlist}};
}
sub
declare_config {
(
my
MY
$self
,
my
Template
$tmpl
,
my
(
$ns
,
@args
)) =
@_
;
$tmpl
->configure(
map
{(
$_
->[NODE_PATH],
$_
->[NODE_BODY] // 1)}
@args
);
undef
;
}
sub
declare_constants {
(
my
MY
$self
,
my
Template
$tmpl
,
my
(
$ns
,
@args
)) =
@_
;
$tmpl
->{cf_constants} = \
@args
;
undef
;
}
sub
location2name {
(
my
MY
$self
,
my
$location
) =
@_
;
$location
=~ s{([^A-Za-z0-9])}{
'_'
.
sprintf
(
"%02x"
,
unpack
(
"C"
, $1))}eg;
$location
;
}
sub
parse_location {
(
my
MY
$self
,
my
(
$location
,
$name
,
$item
)) =
@_
;
return
unless
$location
=~ m{^/};
$self
->subroutes->create([
$name
,
$location
],
$item
);
}
sub
subroutes {
(
my
MY
$self
) =
@_
;
$self
->{subroutes} //=
$self
->SubRoutes->new;
}
sub
SubRoutes {
'YATT::Lite::WebMVC0::SubRoutes'
}
sub
primary_ns {
my
MY
$self
=
shift
;
unless
(
$self
->{cf_namespace}) {
'yatt'
;
}
else
{
first(
$self
->{cf_namespace});
}
}
sub
namespace {
my
MY
$self
=
shift
;
return
unless
defined
$self
->{cf_namespace};
ref
$self
->{cf_namespace} &&
wantarray
? @{
$self
->{cf_namespace}}
:
$self
->{cf_namespace};
}
sub
add_part {
(
my
MY
$self
,
my
Template
$tmpl
,
my
Part
$part
,
my
$no_conflict_check
) =
@_
;
my
$itemKey
=
$part
->item_key;
if
(not
$no_conflict_check
and
defined
$tmpl
->{Item}{
$itemKey
}) {
die
$self
->synerror_at(
$self
->{startln},
q{Conflicting part name! '%s'}
,
$part
->{cf_name});
}
if
(
$tmpl
->{partlist} and
my
Part
$prev
=
$tmpl
->{partlist}[-1]) {
$prev
->{cf_endln} =
$self
->{endln};
}
$part
->{cf_startln} =
$self
->{startln};
$part
->{cf_bodyln} =
$self
->{endln};
push
@{
$tmpl
->{partlist}},
$tmpl
->{Item}{
$itemKey
} =
$part
;
}
sub
add_route {
(
my
MY
$self
,
my
Part
$part
,
my
$mapping
) =
@_
;
$mapping
->configure(
item
=>
$part
);
$self
->{subroutes}->append(
$mapping
);
$self
->add_url_params(
$part
, lexpand(
$mapping
->cget(
'params'
)));
}
sub
add_text {
(
my
MY
$self
,
my
Part
$part
,
my
$text
) =
@_
;
push
@{
$part
->{toks}},
$text
;
$self
->add_posinfo(
length
(
$text
), 1);
$self
->{startln} =
$self
->{endln} += numLines(
$text
);
}
sub
add_lineinfo {
(
my
MY
$self
,
my
$sink
) =
@_
;
}
sub
parse_arg_spec_for_part {
(
my
MY
$self
,
my
Part
$part
,
my
$attNode
) =
@_
;
my
(
$node_type
,
$lno
,
$argName
,
$desc
)
= @{
$attNode
}[NODE_TYPE, NODE_LNO, NODE_PATH, NODE_BODY];
my
(
$type
,
$dflag
,
$default
);
if
(
$node_type
== TYPE_ATT_NESTED) {
$type
=
$desc
->[NODE_PATH] ||
$desc
->[NODE_BODY];
}
else
{
(
$type
,
$dflag
,
$default
) =
split
m{([|/?!])},
$desc
||
''
, 2;
};
(
$type
,
$argName
, nextArgNo(
$part
)
,
$lno
,
$node_type
,
$dflag
,
defined
$default
?
$self
->_parse_text_entities(
$default
) :
undef
);
}
sub
add_args {
(
my
MY
$self
,
my
Part
$part
) =
splice
@_
, 0, 2;
foreach
my
$argSpec
(
@_
) {
my
(
$node_type
,
$lno
,
$argName
,
$desc
)
= @{
$argSpec
}[NODE_TYPE, NODE_LNO, NODE_PATH, NODE_BODY];
unless
(
defined
$argName
) {
die
$self
->synerror_at(
$self
->{startln},
'argName is empty!'
);
}
my
(
$type
,
$dflag
,
$default
);
if
(
$node_type
== TYPE_ATT_NESTED) {
my
$headDesc
=
$desc
->[0];
$type
=
$headDesc
->[NODE_PATH] ||
$headDesc
->[NODE_BODY];
}
else
{
(
$type
,
$dflag
,
$default
) =
$self
->parse_type_dflag_default(
$desc
);
};
if
(
my
$var
=
$part
->{arg_dict}{
$argName
}) {
if
(
$var
->from_route) {
$self
->set_var_type(
$var
,
$type
);
$self
->set_dflag_default_to(
$var
,
$dflag
,
$default
);
}
else
{
die
$self
->synerror_at(
$self
->{startln}
,
'Argument %s redefined in %s %s'
,
$argName
,
$part
->{cf_kind},
$part
->{cf_name});
}
}
else
{
my
$var
=
$self
->mkvar_at(
$self
->{startln}
,
$type
,
$argName
, nextArgNo(
$part
)
,
$lno
,
$node_type
);
$self
->set_dflag_default_to(
$var
,
$dflag
,
$default
);
my
$type
=
$var
->type->[0];
if
(
$node_type
== TYPE_ATT_NESTED) {
my
$sub
=
$self
->can(
"add_arg_of_type_$type"
)
or
die
$self
->synerror_at(
$self
->{startln},
"Unknown arg type in arg '%s': %s"
,
$argName
,
$type
);
$sub
->(
$self
,
$part
,
$var
,
$desc
);
}
else
{
if
(
my
$sub
=
$self
->can(
"add_arg_of_type_$type"
)) {
$sub
->(
$self
,
$part
,
$var
, []);
}
else
{
push
@{
$part
->{arg_order}},
$argName
;
$part
->{arg_dict}{
$argName
} =
$var
;
}
}
}
}
$self
;
}
sub
add_url_params {
(
my
MY
$self
,
my
Part
$part
,
my
@params
) =
@_
;
foreach
my
$param
(
@params
) {
my
(
$argName
,
$type_or_pat
) =
@$param
;
my
$type
=
'value'
;
my
$var
=
$self
->mkvar_at(
$self
->{startln},
$type
,
$argName
, nextArgNo(
$part
));
$var
->from_route(1);
push
@{
$part
->{arg_order}},
$argName
;
$part
->{arg_dict}{
$argName
} =
$var
;
}
}
sub
add_arg_of_type_code {
(
my
MY
$self
,
my
Part
$part
,
my
(
$var
,
$attlist
)) =
@_
;
$var
->widget(
my
Widget
$virtual
=
$self
->Widget->new(
name
=>
$var
->varname));
$self
->add_args(
$virtual
,
@$attlist
);
my
$argName
=
$var
->varname;
push
@{
$part
->{arg_order}},
$argName
;
$part
->{arg_dict}{
$argName
} =
$var
;
}
sub
add_arg_of_type_delegate {
(
my
MY
$self
,
my
Widget
$widget
,
my
(
$var
,
$attlist
)) =
@_
;
my
$name
=
$var
->varname;
$widget
->{var_dict}{
$name
} =
$var
;
my
(
$type
,
@subtype
) = @{
$var
->type};
my
@wpath
=
@subtype
?
@subtype
:
$name
;
my
Widget
$delegate
=
$self
->{cf_vfs}->find_part_from
(
$widget
->{cf_folder},
@wpath
) or
do
{
$self
->synerror_at(
$self
->{startln},
"Can't find delegate widget for argument %s=[%s]"
,
$name
,
join
(
":"
,
$type
,
@subtype
));
};
$var
->weakened_set_widget(
$delegate
);
unless
(Scalar::Util::isweak(
$var
->[YATT::Lite::VarTypes::t_delegate::VSLOT_WIDGET])) {
die
"Can't weaken!"
;
}
$var
->delegate_vars(\
my
%delegate_vars
);
my
(
$attDict
,
$excludeDict
) =
do
{
my
(
%attDict
,
%exclDict
);
foreach
my
$argSpec
(
@$attlist
) {
if
(
my
$attName
=
$argSpec
->[NODE_PATH]) {
defined
$attDict
{
$attName
}
and
die
$self
->synerror_at
(
$argSpec
->[NODE_LNO]
,
"Duplicate argname '%s' in delegate var %s"
,
$attName
,
$name
);
$attDict
{
$attName
} =
$argSpec
;
}
elsif
(
$argSpec
->[NODE_TYPE] == TYPE_ATT_TEXT
and (
$attName
) =
$argSpec
->[NODE_BODY] =~ /^-(\w+)$/) {
if
(not
$delegate
->{arg_dict}{
$attName
}) {
die
$self
->synerror_at
(
$argSpec
->[NODE_LNO]
,
"No such argument '%s' in delegate to '%s'"
,
$attName
,
$name
);
}
$exclDict
{
$attName
} =
$argSpec
;
}
else
{
die
$self
->synerror_at
(
$argSpec
->[NODE_LNO]
,
"Invalid decl spec for delegate var %s"
,
$name
);
}
}
(\
%attDict
, \
%exclDict
);
};
foreach
my
$argName
(@{
$delegate
->{arg_order}}) {
next
if
$widget
->{arg_dict}{
$argName
};
next
if
$excludeDict
->{
$argName
};
$delegate_vars
{
$argName
} =
my
$orig
=
$delegate
->{arg_dict}{
$argName
};
my
$actual
=
do
{
if
(
my
$att
=
$attDict
->{
$argName
}) {
my
@new
=
$self
->parse_arg_spec_for_part(
$widget
,
$att
);
$new
[0] ||=
$orig
->[0];
$self
->mkvar_at(
$self
->{startln},
@new
);
}
else
{
$self
->mkvar_at(
$widget
->{cf_startln},
@$orig
)
->argno(nextArgNo(
$widget
))->lineno(
$widget
->{cf_startln});
}
};
$widget
->{arg_dict}{
$argName
} =
$actual
;
push
@{
$widget
->{arg_order}},
$argName
;
}
}
sub
nextArgNo {
(
my
Part
$part
) =
@_
;
$part
->{arg_order} ?
scalar
@{
$part
->{arg_order}} : 0;
}
sub
synerror_at {
(
my
MY
$self
,
my
$ln
) =
splice
@_
, 0, 2;
my
%opts
= (
$self
->_tmpl_file_line(
$ln
),
depth
=> 2);
$self
->_error(\
%opts
,
@_
);
}
sub
_error {
(
my
MY
$self
,
my
(
$opts
,
$fmt
)) =
splice
@_
, 0, 3;
if
(
my
$vfs
=
$self
->{cf_vfs}) {
$vfs
->error(
$opts
,
$fmt
,
@_
);
}
else
{
sprintf
(
$fmt
,
@_
);
}
}
sub
_tmpl_file_line {
(
my
MY
$self
,
my
$ln
) =
@_
;
(
$$self
{cf_path} ? (
tmpl_file
=>
$$self
{cf_path}) : ()
,
defined
$ln
? (
tmpl_line
=>
$ln
) : ());
}
sub
is_ident {
return
undef
unless
defined
$_
[0];
local
%+;
$_
[0] =~ m{^[[:alpha:]_\:](?:\w+|:)*$};
}
sub
oneof {
my
$hash
=
shift
;
my
$i
= 0;
foreach
my
$key
(
@_
) {
if
(
defined
(
my
$value
=
$hash
->{
$key
})) {
return
$i
=>
$value
;
}
}
continue
{
$i
++;
}
die
"really??"
;
}
sub
first {
ref
$_
[0] ?
$_
[0][0] :
$_
[0] }
sub
nonmatched {
return
unless
defined
$_
[0] and
length
$_
[0];
$_
[0];
}
sub
shortened_original_entpath {
(
my
MY
$self
) =
@_
;
my
$str
=
$self
->{_original_entpath};
$str
=~ s/\n.*\z//s;
$str
;
}
sub
_parse_body;
sub
_parse_text_entities_at;
sub
_parse_text_entities;
sub
_parse_entpath;
sub
_parse_pipeline;
sub
_parse_entgroup;
sub
_parse_entterm;
sub
_parse_group_string;
sub
_parse_hash;
sub
DESTROY {}
sub
AUTOLOAD {
unless
(
ref
$_
[0]) {
confess
"BUG! \$self isn't object!"
;
}
my
$sub
=
our
$AUTOLOAD
;
(
my
$meth
=
$sub
) =~ s/.*:://;
my
$sym
=
$YATT::Lite::LRXML::
{
$meth
}
or croak
"No such method: $meth"
;
if
(
$meth
=~ /ent/) {
}
elsif
(
$meth
=~ /body/) {
}
else
{
my
MY
$self
=
$_
[0];
die
$self
->synerror_at(
$self
->{startln},
"Unknown method: %s"
,
$meth
);
}
my
$code
= *{
$sym
}{CODE}
or croak
"Can't find definition of: $meth"
;
goto
&$code
;
}
break_load_parser();
1;