tamer: NIR->xmli template definition setup
This sets the stage for template parsing, and finally decides how we're going to represent templates on the ASG. This is going to start simple, since my original plans for improving how templates are handled (conceptually) is going to have to wait. This is the last difficult object type to figure out, with respect to graph representation and derivation, so I wanted to get it out of the way. DEV-13708main
parent
08278bc867
commit
d42a46d2b8
|
@ -22,6 +22,7 @@ use super::{
|
|||
Asg, AsgError, ExprOp, FragmentText, IdentKind, ObjectIndex, Source,
|
||||
};
|
||||
use crate::{
|
||||
asg::graph::object::Tpl,
|
||||
f::Functor,
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
parse::{self, util::SPair, ParseState, Token, Transition, Transitionable},
|
||||
|
@ -158,6 +159,23 @@ pub enum Air {
|
|||
/// and the span is intended to aid in tracking down why rooting
|
||||
/// occurred.
|
||||
IdentRoot(SPair),
|
||||
|
||||
/// Create a new [`Tpl`] on the graph and switch to template parsing.
|
||||
///
|
||||
/// Until [`Self::TplClose`] is found,
|
||||
/// all parsed objects will be parented to the [`Tpl`] rather than the
|
||||
/// parent [`Pkg`].
|
||||
/// Template parsing also recognizes additional nodes that can appear
|
||||
/// only in this mode.
|
||||
///
|
||||
/// The [`ExprStack`] will be [`Held`],
|
||||
/// to be restored after template parsing has concluded.
|
||||
TplOpen(Span),
|
||||
|
||||
/// Close the active [`Tpl`] and exit template parsing.
|
||||
///
|
||||
/// The [`ExprStack`] will be restored to its prior state.
|
||||
TplClose(Span),
|
||||
}
|
||||
|
||||
impl Token for Air {
|
||||
|
@ -174,7 +192,9 @@ impl Token for Air {
|
|||
PkgOpen(span)
|
||||
| PkgClose(span)
|
||||
| ExprOpen(_, span)
|
||||
| ExprClose(span) => *span,
|
||||
| ExprClose(span)
|
||||
| TplOpen(span)
|
||||
| TplClose(span) => *span,
|
||||
|
||||
BindIdent(spair)
|
||||
| ExprRef(spair)
|
||||
|
@ -236,6 +256,14 @@ impl Display for Air {
|
|||
IdentRoot(sym) => {
|
||||
write!(f, "rooting of identifier {}", TtQuote::wrap(sym))
|
||||
}
|
||||
|
||||
TplOpen(name) => {
|
||||
write!(f, "open template {}", TtQuote::wrap(name))
|
||||
}
|
||||
|
||||
TplClose(_) => {
|
||||
write!(f, "close template")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -292,6 +320,13 @@ pub struct Dormant;
|
|||
/// Expression stack is in use as part of an expression parse.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Active(StackEdge);
|
||||
/// Expression stack has been set aside temporarily for some other operation
|
||||
/// and will be restored after that operation completes.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Held {
|
||||
Dormant,
|
||||
Active(Active),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum StackEdge {
|
||||
|
@ -334,6 +369,16 @@ impl ExprStack<Dormant> {
|
|||
let Self(stack, _) = self;
|
||||
ExprStack(stack, Active(StackEdge::Dangling))
|
||||
}
|
||||
|
||||
/// Set the expression stack aside to perform another operation.
|
||||
///
|
||||
/// The stack can later be restored using [`ExprStack::release_st`],
|
||||
/// and will restore to the same dormant state.
|
||||
fn hold(self) -> ExprStack<Held> {
|
||||
match self {
|
||||
Self(stack, _st) => ExprStack(stack, Held::Dormant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExprStack<Active> {
|
||||
|
@ -398,6 +443,24 @@ impl ExprStack<Active> {
|
|||
}
|
||||
}
|
||||
|
||||
impl ExprStack<Held> {
|
||||
/// Produce an [`AirAggregate`] state from a prior expression stacks
|
||||
/// state.
|
||||
///
|
||||
/// This marks the completion of whatever operation caused the stack to
|
||||
/// be held using one of the `hold` implementations.
|
||||
fn release_st(self, oi_pkg: ObjectIndex<Pkg>) -> AirAggregate {
|
||||
match self {
|
||||
Self(stack, Held::Dormant) => {
|
||||
AirAggregate::PkgDfn(oi_pkg, ExprStack(stack, Dormant))
|
||||
}
|
||||
Self(_stack, Held::Active(_active)) => {
|
||||
todo!("ExprStack<Held> -> Active")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ExprStack<Dormant> {
|
||||
fn default() -> Self {
|
||||
// TODO: 16 is a generous guess that is very unlikely to be exceeded
|
||||
|
@ -429,6 +492,19 @@ impl Display for ExprStack<Active> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for ExprStack<Held> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self(_, Held::Dormant) => {
|
||||
write!(f, "held dormant expression stack")
|
||||
}
|
||||
Self(_, Held::Active(..)) => {
|
||||
write!(f, "held active expression stack")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// AIR parser state.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AirAggregate {
|
||||
|
@ -442,6 +518,17 @@ pub enum AirAggregate {
|
|||
///
|
||||
/// Expressions may be nested arbitrarily deeply.
|
||||
BuildingExpr(ObjectIndex<Pkg>, ExprStack<Active>, ObjectIndex<Expr>),
|
||||
|
||||
/// Parser is in template parsing mode.
|
||||
///
|
||||
/// All objects encountered until the closing [`Air::TplClose`] will be
|
||||
/// parented to this template rather than the parent [`Pkg`].
|
||||
/// See [`Air::TplOpen`] for more information.
|
||||
BuildingTpl(
|
||||
(ObjectIndex<Pkg>, ExprStack<Held>),
|
||||
ObjectIndex<Tpl>,
|
||||
Option<SPair>,
|
||||
),
|
||||
}
|
||||
|
||||
impl Default for AirAggregate {
|
||||
|
@ -460,6 +547,16 @@ impl Display for AirAggregate {
|
|||
BuildingExpr(_, es, _) => {
|
||||
write!(f, "building expression with {es}")
|
||||
}
|
||||
BuildingTpl((_, es), _, None) => {
|
||||
write!(f, "building anonymous template with {es}")
|
||||
}
|
||||
BuildingTpl((_, es), _, Some(name)) => {
|
||||
write!(
|
||||
f,
|
||||
"building named template {} with {es}",
|
||||
TtQuote::wrap(name)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -482,6 +579,7 @@ impl ParseState for AirAggregate {
|
|||
use Air::*;
|
||||
use AirAggregate::*;
|
||||
|
||||
// TODO: Seems to be about time for refactoring this...
|
||||
match (self, tok) {
|
||||
(st, Todo) => Transition(st).incomplete(),
|
||||
|
||||
|
@ -518,8 +616,8 @@ impl ParseState for AirAggregate {
|
|||
Transition(BuildingExpr(oi_pkg, es.push(poi), oi)).incomplete()
|
||||
}
|
||||
|
||||
(st @ Empty(..), ExprOpen(_, span)) => {
|
||||
Transition(st).err(AsgError::InvalidExprContext(span))
|
||||
(st @ Empty(..), ExprOpen(_, span) | TplOpen(span)) => {
|
||||
Transition(st).err(AsgError::PkgExpected(span))
|
||||
}
|
||||
|
||||
(st @ (Empty(..) | PkgDfn(..)), ExprClose(span)) => {
|
||||
|
@ -607,6 +705,34 @@ impl ParseState for AirAggregate {
|
|||
Transition(st).incomplete()
|
||||
}
|
||||
|
||||
(PkgDfn(oi_pkg, es), TplOpen(span)) => {
|
||||
let oi_tpl = asg.create(Tpl::new(span));
|
||||
|
||||
Transition(BuildingTpl((oi_pkg, es.hold()), oi_tpl, None))
|
||||
.incomplete()
|
||||
}
|
||||
|
||||
(BuildingExpr(..), TplOpen(_span)) => todo!("BuildingExpr TplOpen"),
|
||||
|
||||
(BuildingTpl((oi_pkg, es), oi_tpl, None), BindIdent(name)) => asg
|
||||
.lookup_or_missing(name)
|
||||
.bind_definition(asg, name, oi_tpl)
|
||||
.map(|oi_ident| oi_pkg.defines(asg, oi_ident))
|
||||
.map(|_| ())
|
||||
.transition(BuildingTpl((oi_pkg, es), oi_tpl, Some(name))),
|
||||
|
||||
(BuildingTpl((oi_pkg, es), oi_tpl, _), TplClose(span)) => {
|
||||
oi_tpl.close(asg, span);
|
||||
Transition(es.release_st(oi_pkg)).incomplete()
|
||||
}
|
||||
|
||||
(BuildingTpl(..), tok) => todo!("BuildingTpl body: {tok:?}"),
|
||||
|
||||
(
|
||||
st @ (Empty(..) | PkgDfn(..) | BuildingExpr(..)),
|
||||
TplClose(span),
|
||||
) => Transition(st).err(AsgError::UnbalancedTpl(span)),
|
||||
|
||||
(
|
||||
st,
|
||||
tok @ (IdentDecl(..) | IdentExternDecl(..) | IdentDep(..)
|
||||
|
|
|
@ -356,7 +356,7 @@ fn expr_without_pkg() {
|
|||
|
||||
assert_eq!(
|
||||
vec![
|
||||
Err(ParseError::StateError(AsgError::InvalidExprContext(S1))),
|
||||
Err(ParseError::StateError(AsgError::PkgExpected(S1))),
|
||||
// RECOVERY
|
||||
Ok(Parsed::Incomplete), // PkgOpen
|
||||
Ok(Parsed::Incomplete), // PkgClose
|
||||
|
@ -1119,6 +1119,34 @@ fn idents_share_defining_pkg() {
|
|||
)
|
||||
}
|
||||
|
||||
// A template is defined by the package containing it,
|
||||
// like an expression.
|
||||
#[test]
|
||||
fn tpl_defining_pkg() {
|
||||
let id_tpl = SPair("_tpl_".into(), S3);
|
||||
|
||||
let toks = vec![
|
||||
Air::PkgOpen(S1),
|
||||
Air::TplOpen(S2),
|
||||
Air::BindIdent(id_tpl),
|
||||
Air::TplClose(S4),
|
||||
Air::PkgClose(S5),
|
||||
];
|
||||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
assert!(sut.all(|x| x.is_ok()));
|
||||
let asg = sut.finalize().unwrap().into_context();
|
||||
|
||||
let tpl = asg.expect_ident_obj::<Tpl>(id_tpl);
|
||||
assert_eq!(S2.merge(S4).unwrap(), tpl.span());
|
||||
|
||||
let oi_id_tpl = asg.lookup(id_tpl).unwrap();
|
||||
assert_eq!(
|
||||
S1.merge(S5),
|
||||
oi_id_tpl.src_pkg(&asg).map(|pkg| pkg.resolve(&asg).span()),
|
||||
);
|
||||
}
|
||||
|
||||
fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
|
||||
where
|
||||
I::IntoIter: Debug,
|
||||
|
|
|
@ -34,6 +34,16 @@ use crate::{
|
|||
use super::TransitionError;
|
||||
|
||||
/// An error from an ASG operation.
|
||||
///
|
||||
///
|
||||
/// Note that the user may encounter an equivalent error in the source
|
||||
/// document format
|
||||
/// (e.g. XML via [XIR->NIR lowering](crate::nir))
|
||||
/// and therefore may never see some of these errors.
|
||||
/// However,
|
||||
/// a source IR _may_ choose to allow certain errors through to ease the
|
||||
/// burden on its maintenance/development,
|
||||
/// or a system may utilize this IR directly.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AsgError {
|
||||
/// An object could not change state in the manner requested.
|
||||
|
@ -68,7 +78,7 @@ pub enum AsgError {
|
|||
InvalidPkgCloseContext(Span),
|
||||
|
||||
/// Attempted to open an expression in an invalid context.
|
||||
InvalidExprContext(Span),
|
||||
PkgExpected(Span),
|
||||
|
||||
/// An expresion is not reachable by any other expression or
|
||||
/// identifier.
|
||||
|
@ -85,17 +95,12 @@ pub enum AsgError {
|
|||
|
||||
/// Attempted to close an expression with no corresponding opening
|
||||
/// delimiter.
|
||||
///
|
||||
/// Note that the user may encounter an equivalent error in the source
|
||||
/// document format
|
||||
/// (e.g. XML via [XIR->NIR lowering](crate::nir))
|
||||
/// and therefore may never see this error.
|
||||
/// However,
|
||||
/// a source IR _may_ choose to allow improperly nested expressions
|
||||
/// through to this IR,
|
||||
/// or may utilize this IR directly.
|
||||
UnbalancedExpr(Span),
|
||||
|
||||
/// Attempted to close a template with no corresponding opening
|
||||
/// delimiter.
|
||||
UnbalancedTpl(Span),
|
||||
|
||||
/// Attempted to bind the an identifier to an expression while not in an
|
||||
/// expression context.
|
||||
///
|
||||
|
@ -123,12 +128,13 @@ impl Display for AsgError {
|
|||
InvalidPkgCloseContext(_) => {
|
||||
write!(f, "invalid context for package close",)
|
||||
}
|
||||
InvalidExprContext(_) => write!(f, "invalid expression context"),
|
||||
PkgExpected(_) => write!(f, "expected package definition"),
|
||||
DanglingExpr(_) => write!(
|
||||
f,
|
||||
"dangling expression (anonymous expression has no parent)"
|
||||
),
|
||||
UnbalancedExpr(_) => write!(f, "unbalanced expression"),
|
||||
UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
|
||||
InvalidExprBindContext(_) => {
|
||||
write!(f, "invalid expression identifier binding context")
|
||||
}
|
||||
|
@ -197,8 +203,8 @@ impl Diagnostic for AsgError {
|
|||
),
|
||||
],
|
||||
|
||||
InvalidExprContext(span) => {
|
||||
vec![span.error("an expression is not allowed here")]
|
||||
PkgExpected(span) => {
|
||||
vec![span.error("a package definition was expected here")]
|
||||
}
|
||||
|
||||
DanglingExpr(span) => vec![
|
||||
|
@ -214,7 +220,11 @@ impl Diagnostic for AsgError {
|
|||
],
|
||||
|
||||
UnbalancedExpr(span) => {
|
||||
vec![span.error("there is no open expression to end here")]
|
||||
vec![span.error("there is no open expression to close here")]
|
||||
}
|
||||
|
||||
UnbalancedTpl(span) => {
|
||||
vec![span.error("there is no open template to close here")]
|
||||
}
|
||||
|
||||
InvalidExprBindContext(span) => vec![
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
use super::{
|
||||
super::{Asg, AsgError, ObjectIndex, ObjectKind},
|
||||
Expr, Object, ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy,
|
||||
ObjectRelatable, Pkg,
|
||||
ObjectRelatable, Pkg, Tpl,
|
||||
};
|
||||
use crate::{
|
||||
diagnose::{Annotate, Diagnostic},
|
||||
|
@ -983,6 +983,7 @@ object_rel! {
|
|||
Ident -> {
|
||||
tree Ident,
|
||||
tree Expr,
|
||||
tree Tpl,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,15 +24,29 @@ use std::fmt::Display;
|
|||
use super::{
|
||||
Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy, ObjectRelatable,
|
||||
};
|
||||
use crate::span::Span;
|
||||
use crate::{asg::Asg, f::Functor, span::Span};
|
||||
|
||||
/// Template.
|
||||
/// Template with associated name.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Tpl;
|
||||
pub struct Tpl(Span);
|
||||
|
||||
impl Tpl {
|
||||
pub fn span(&self) -> Span {
|
||||
todo!("Tpl::span")
|
||||
match self {
|
||||
Self(span) => *span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(span: Span) -> Self {
|
||||
Self(span)
|
||||
}
|
||||
}
|
||||
|
||||
impl Functor<Span> for Tpl {
|
||||
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
|
||||
match self {
|
||||
Self(span) => Self(f(span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,3 +62,17 @@ object_rel! {
|
|||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectIndex<Tpl> {
|
||||
/// Complete a template definition.
|
||||
///
|
||||
/// This simply updates the span of the template to encompass the entire
|
||||
/// definition.
|
||||
pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
|
||||
self.map_obj(asg, |tpl| {
|
||||
tpl.map(|open_span| {
|
||||
open_span.merge(close_span).unwrap_or(open_span)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
use super::object::{
|
||||
DynObjectRel, Expr, Object, ObjectIndex, ObjectRelTy, OiPairObjectInner,
|
||||
Pkg,
|
||||
Pkg, Tpl,
|
||||
};
|
||||
use crate::{
|
||||
asg::{
|
||||
|
@ -40,7 +40,7 @@ use crate::{
|
|||
},
|
||||
diagnose::Annotate,
|
||||
diagnostic_panic, diagnostic_unreachable,
|
||||
parse::{prelude::*, Transitionable},
|
||||
parse::{prelude::*, util::SPair, Transitionable},
|
||||
span::{Span, UNKNOWN_SPAN},
|
||||
sym::{
|
||||
st::{URI_LV_CALC, URI_LV_RATER, URI_LV_TPL},
|
||||
|
@ -185,7 +185,9 @@ impl<'a> TreeContext<'a> {
|
|||
self.emit_expr(expr, paired_rel.source(), depth)
|
||||
}
|
||||
|
||||
Object::Tpl((tpl, oi)) => todo!("tpl: {tpl:?}, {oi:?}"),
|
||||
Object::Tpl((tpl, _)) => {
|
||||
self.emit_template(tpl, paired_rel.source(), depth)
|
||||
}
|
||||
|
||||
Object::Root(..) => diagnostic_unreachable!(
|
||||
vec![],
|
||||
|
@ -265,6 +267,28 @@ impl<'a> TreeContext<'a> {
|
|||
))
|
||||
}
|
||||
|
||||
/// Emit a template definition.
|
||||
fn emit_template(
|
||||
&mut self,
|
||||
tpl: &Tpl,
|
||||
src: &Object<OiPairObjectInner>,
|
||||
depth: Depth,
|
||||
) -> Option<Xirf> {
|
||||
match src {
|
||||
Object::Ident((ident, _)) => {
|
||||
self.push(attr_name(ident.name()));
|
||||
|
||||
Some(Xirf::open(
|
||||
QN_TEMPLATE,
|
||||
OpenSpan::without_name_span(tpl.span()),
|
||||
depth,
|
||||
))
|
||||
}
|
||||
|
||||
_ => todo!("emit_template: {src:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, tok: Xirf) {
|
||||
if self.stack.is_full() {
|
||||
diagnostic_panic!(
|
||||
|
@ -320,6 +344,10 @@ fn ns(qname: QName, uri: UriStaticSymbolId, span: Span) -> Xirf {
|
|||
Xirf::attr(qname, uri, (span, span))
|
||||
}
|
||||
|
||||
fn attr_name(name: SPair) -> Xirf {
|
||||
Xirf::attr(QN_NAME, name, (name.span(), name.span()))
|
||||
}
|
||||
|
||||
fn expr_ele(expr: &Expr, depth: Depth) -> Xirf {
|
||||
use ExprOp::*;
|
||||
|
||||
|
|
|
@ -195,6 +195,8 @@ pub enum NirEntity {
|
|||
/// Disjunctive (∨) expression.
|
||||
Any,
|
||||
|
||||
/// Template.
|
||||
Tpl,
|
||||
/// Template parameter (metavariable).
|
||||
TplParam,
|
||||
}
|
||||
|
@ -224,6 +226,7 @@ impl Display for NirEntity {
|
|||
All => write!(f, "conjunctive (∧) expression"),
|
||||
Any => write!(f, "disjunctive (∨) expression"),
|
||||
|
||||
Tpl => write!(f, "template"),
|
||||
TplParam => write!(f, "template param (metavariable)"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,6 +87,14 @@ impl ParseState for NirToAir {
|
|||
Transition(Ready).ok(Air::ExprOpen(ExprOp::Disj, span))
|
||||
}
|
||||
|
||||
(Ready, Nir::Open(NirEntity::Tpl, span)) => {
|
||||
Transition(Ready).ok(Air::TplOpen(span))
|
||||
}
|
||||
|
||||
(Ready, Nir::Close(NirEntity::Tpl, span)) => {
|
||||
Transition(Ready).ok(Air::TplClose(span))
|
||||
}
|
||||
|
||||
(
|
||||
Ready,
|
||||
Nir::Close(
|
||||
|
|
|
@ -116,3 +116,24 @@ fn logic_exprs() {
|
|||
Sut::parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tpl_with_name() {
|
||||
let name = SPair("_tpl_name_".into(), S2);
|
||||
|
||||
let toks = vec![
|
||||
Nir::Open(NirEntity::Tpl, S1),
|
||||
Nir::BindIdent(name),
|
||||
Nir::Close(NirEntity::Tpl, S3),
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
O(Air::TplOpen(S1)),
|
||||
O(Air::BindIdent(name)),
|
||||
O(Air::TplClose(S3)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1390,11 +1390,12 @@ ele_parse! {
|
|||
/// See also [`InlineTemplate`] for template definitions.
|
||||
///
|
||||
/// Templates are applied using [`ApplyTemplate`] or [`TplApplyShort`].
|
||||
TemplateStmt := QN_TEMPLATE {
|
||||
TemplateStmt := QN_TEMPLATE(_, ospan) {
|
||||
@ {
|
||||
QN_NAME => TodoAttr,
|
||||
QN_NAME => BindIdent,
|
||||
QN_DESC => TodoAttr,
|
||||
} => Todo,
|
||||
} => NirEntity::Tpl.open(ospan),
|
||||
/(cspan) => NirEntity::Tpl.close(cspan),
|
||||
|
||||
TplHeading,
|
||||
AnyStmtOrExpr,
|
||||
|
|
|
@ -31,4 +31,6 @@
|
|||
</any>
|
||||
<any />
|
||||
</classify>
|
||||
|
||||
<template name="_empty_" />
|
||||
</package>
|
||||
|
|
|
@ -35,5 +35,6 @@
|
|||
</any>
|
||||
<any />
|
||||
</classify>
|
||||
</package>
|
||||
|
||||
<template name="_empty_" />
|
||||
</package>
|
||||
|
|
Loading…
Reference in New Issue