tamer: asg: Initial nested template supported
I had hoped this would be considerably easier to implement, but there are some confounding factors. First of all: this accomplishes the initial task of getting nested template applications and definitions re-output in the `xmli` file. But to do so successfully, some assumptions had to be made. The primary issue is that of scope. The old (XSLT-based) TAME relied on the output JS to handle lexical scope for it at runtime in most situations. In the case of the template system, when scoping/shadowing were needed, complex and buggy XPaths were used to make a best effort. The equivalent here would be a graph traversal, which is not ideal. I had begun going down the rabbit hole of formalizing lexical scope for TAMER with environments, but I want to get this committed and working first; I've been holding onto this and breaking off changes for some time now. DEV-13708main
parent
a738a05461
commit
daa8c6967b
|
@ -36,7 +36,7 @@
|
|||
//! air such cringeworthy dad jokes here.
|
||||
|
||||
use super::{
|
||||
graph::object::{ObjectIndexRelTo, ObjectIndexTo, Pkg, Tpl},
|
||||
graph::object::{ObjectIndexTo, ObjectIndexToTree, Pkg, Tpl},
|
||||
Asg, AsgError, Expr, Ident, ObjectIndex,
|
||||
};
|
||||
use crate::{
|
||||
|
@ -262,6 +262,34 @@ impl AirAggregate {
|
|||
PkgTpl(st) => st.is_accepting(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
/// The rooting context for [`Ident`]s for the active parser.
|
||||
///
|
||||
/// A value of [`None`] indicates that the current parser does not
|
||||
/// support direct bindings,
|
||||
/// but a parent context may
|
||||
/// (see [`AirAggregateCtx::rooting_oi`]).
|
||||
fn active_rooting_oi(&self) -> Option<ObjectIndexToTree<Ident>> {
|
||||
match self {
|
||||
AirAggregate::Empty => None,
|
||||
AirAggregate::Toplevel(pkg_oi) => Some((*pkg_oi).into()),
|
||||
|
||||
// Expressions never serve as roots for identifiers;
|
||||
// this will always fall through to the parent context.
|
||||
// Since the parent context is a package or a template,
|
||||
// the next frame should succeed.
|
||||
AirAggregate::PkgExpr(_) => None,
|
||||
|
||||
// Identifiers bound while within a template definition context
|
||||
// must bind to the eventual _expansion_ site,
|
||||
// as if the body were pasted there.
|
||||
// Templates must therefore serve as containers for identifiers
|
||||
// bound therein.
|
||||
AirAggregate::PkgTpl(tplst) => {
|
||||
tplst.active_tpl_oi().map(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional parser context,
|
||||
|
@ -337,18 +365,20 @@ impl AirAggregateCtx {
|
|||
/// control to the child `to`.
|
||||
///
|
||||
/// See also [`Self::proxy`].
|
||||
fn ret_or_transfer(
|
||||
fn ret_or_transfer<S: Into<AirAggregate>, SB: Into<AirAggregate>>(
|
||||
&mut self,
|
||||
st: AirAggregate,
|
||||
st: S,
|
||||
tok: impl Token + Into<Air>,
|
||||
to: impl Into<AirAggregate>,
|
||||
to: SB,
|
||||
) -> TransitionResult<AirAggregate> {
|
||||
if st.active_is_accepting(self) {
|
||||
let st_super = st.into();
|
||||
|
||||
if st_super.active_is_accepting(self) {
|
||||
// TODO: dead state or error
|
||||
self.stack().ret_or_dead(AirAggregate::Empty, tok)
|
||||
} else {
|
||||
self.stack().transfer_with_ret(
|
||||
Transition(st),
|
||||
Transition(st_super),
|
||||
Transition(to.into()).incomplete().with_lookahead(tok),
|
||||
)
|
||||
}
|
||||
|
@ -393,28 +423,10 @@ impl AirAggregateCtx {
|
|||
///
|
||||
/// A value of [`None`] indicates that no bindings are permitted in the
|
||||
/// current context.
|
||||
fn rooting_oi(&self) -> Option<(usize, ObjectIndexTo<Ident>)> {
|
||||
fn rooting_oi(&self) -> Option<ObjectIndexToTree<Ident>> {
|
||||
let Self(_, stack, _) = self;
|
||||
|
||||
stack.iter().enumerate().rev().find_map(|(i, st)| match st {
|
||||
AirAggregate::Empty => None,
|
||||
AirAggregate::Toplevel(pkg_oi) => Some((i, (*pkg_oi).into())),
|
||||
|
||||
// Expressions never serve as roots for identifiers;
|
||||
// this will always fall through to the parent context.
|
||||
// Since the parent context is a package or a template,
|
||||
// the next frame should succeed.
|
||||
AirAggregate::PkgExpr(_) => None,
|
||||
|
||||
// Identifiers bound while within a template definition context
|
||||
// must bind to the eventual _expansion_ site,
|
||||
// as if the body were pasted there.
|
||||
// Templates must therefore serve as containers for identifiers
|
||||
// bound therein.
|
||||
AirAggregate::PkgTpl(tplst) => {
|
||||
tplst.active_tpl_oi().map(|oi| (i, oi.into()))
|
||||
}
|
||||
})
|
||||
stack.iter().rev().find_map(|st| st.active_rooting_oi())
|
||||
}
|
||||
|
||||
/// The active dangling expression context for [`Expr`]s.
|
||||
|
@ -457,24 +469,38 @@ impl AirAggregateCtx {
|
|||
fn expansion_oi(&self) -> Option<ObjectIndexTo<Tpl>> {
|
||||
let Self(_, stack, _) = self;
|
||||
|
||||
stack.iter().rev().find_map(|st| match *st {
|
||||
stack.iter().rev().find_map(|st| match st {
|
||||
AirAggregate::Empty => None,
|
||||
AirAggregate::Toplevel(pkg_oi) => Some(pkg_oi.into()),
|
||||
AirAggregate::Toplevel(pkg_oi) => Some((*pkg_oi).into()),
|
||||
AirAggregate::PkgExpr(_) => {
|
||||
diagnostic_todo!(vec![], "PkgExpr expansion_oi")
|
||||
}
|
||||
AirAggregate::PkgTpl(_) => {
|
||||
diagnostic_todo!(vec![], "PkgTpl expansion_oi")
|
||||
AirAggregate::PkgTpl(tplst) => {
|
||||
tplst.active_tpl_oi().map(Into::into)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Root an identifier using the [`Self::rooting_oi`] atop of the stack.
|
||||
fn defines(&mut self, name: SPair) -> Result<ObjectIndex<Ident>, AsgError> {
|
||||
let oi_root = self
|
||||
.rooting_oi()
|
||||
.ok_or(AsgError::InvalidBindContext(name))?;
|
||||
|
||||
Ok(self.lookup_lexical_or_missing(name).add_edge_from(
|
||||
self.asg_mut(),
|
||||
oi_root,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
/// Attempt to locate a lexically scoped identifier,
|
||||
/// or create a new one if missing.
|
||||
///
|
||||
/// Until [`Asg`] can be further generalized,
|
||||
/// there are unfortunately two rooting strategies employed:
|
||||
///
|
||||
/// 1. If the stack has only a single held frame,
|
||||
/// 1. If the stack has only a single held frame at a scope boundary,
|
||||
/// then it is assumed to be the package representing the active
|
||||
/// compilation unit and the identifier is indexed in the global
|
||||
/// scope.
|
||||
|
@ -482,20 +508,45 @@ impl AirAggregateCtx {
|
|||
/// the identifier is defined locally and does not undergo
|
||||
/// indexing.
|
||||
///
|
||||
/// TODO: Generalize this.
|
||||
fn defines(&mut self, name: SPair) -> Result<ObjectIndex<Ident>, AsgError> {
|
||||
let (stacki, oi_root) = self
|
||||
.rooting_oi()
|
||||
.ok_or(AsgError::InvalidBindContext(name))?;
|
||||
/// TODO: This is very informal and just starts to get things working.
|
||||
fn lookup_lexical_or_missing(&mut self, name: SPair) -> ObjectIndex<Ident> {
|
||||
let Self(asg, stack, _) = self;
|
||||
|
||||
let asg = self.asg_mut();
|
||||
let found = stack
|
||||
.iter()
|
||||
.skip(1)
|
||||
.rev()
|
||||
.filter_map(|st| st.active_rooting_oi())
|
||||
.find_map(|oi| asg.lookup(oi, name));
|
||||
|
||||
Ok(match stacki {
|
||||
0 => asg
|
||||
.lookup_global_or_missing(name)
|
||||
.add_edge_from(asg, oi_root, None),
|
||||
_ => oi_root.declare_local(asg, name),
|
||||
})
|
||||
// Rust's borrow checker won't allow us to use unwrap_or_else above
|
||||
// as of 2023-04.
|
||||
if let Some(oi) = found {
|
||||
oi
|
||||
} else {
|
||||
// TODO: This special case can be removed once we generalize
|
||||
// indexing/scope.
|
||||
// TODO: This filtering is a quick kludge to get things working!
|
||||
match stack.iter().filter_map(|st| st.active_rooting_oi()).nth(1) {
|
||||
None => asg.lookup_global_or_missing(name),
|
||||
_ => self.create_env_indexed_ident(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Index an identifier within its environment.
|
||||
///
|
||||
/// TODO: More information as this is formalized.
|
||||
fn create_env_indexed_ident(&mut self, name: SPair) -> ObjectIndex<Ident> {
|
||||
let Self(asg, stack, _) = self;
|
||||
let oi_ident = asg.create(Ident::declare(name));
|
||||
|
||||
stack
|
||||
.iter()
|
||||
.filter_map(|st| st.active_rooting_oi())
|
||||
.for_each(|oi| asg.index_identifier(oi, name, oi_ident));
|
||||
|
||||
oi_ident
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,13 @@ pub enum AirTplAggregate {
|
|||
|
||||
/// Defining a template metavariable.
|
||||
TplMeta(TplState, ObjectIndex<Meta>),
|
||||
|
||||
/// A template has been completed.
|
||||
///
|
||||
/// This is used to determine whether the next token of input ought to
|
||||
/// result in a dead state transition or a transition back to
|
||||
/// [`Self::Ready`].
|
||||
Done,
|
||||
}
|
||||
|
||||
impl Display for AirTplAggregate {
|
||||
|
@ -81,6 +88,7 @@ impl Display for AirTplAggregate {
|
|||
Self::TplMeta(tpl, _) => {
|
||||
write!(f, "building {tpl} metavariable")
|
||||
}
|
||||
Self::Done => write!(f, "completed building template"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,16 +179,15 @@ impl ParseState for AirTplAggregate {
|
|||
use AirTplAggregate::*;
|
||||
|
||||
match (self, tok) {
|
||||
(Ready, AirTpl(TplStart(span))) => {
|
||||
(Ready | Done, AirTpl(TplStart(span))) => {
|
||||
let oi_tpl = ctx.asg_mut().create(Tpl::new(span));
|
||||
|
||||
Transition(Toplevel(TplState::Dangling(oi_tpl))).incomplete()
|
||||
}
|
||||
|
||||
(Toplevel(..), AirTpl(TplStart(span))) => diagnostic_todo!(
|
||||
vec![span.note("for this template")],
|
||||
"nested tpl open"
|
||||
),
|
||||
(st @ Toplevel(..), tok @ AirTpl(TplStart(_))) => {
|
||||
ctx.ret_or_transfer(st, tok, AirTplAggregate::new())
|
||||
}
|
||||
|
||||
(Toplevel(tpl), AirBind(BindIdent(id))) => ctx
|
||||
.defines(id)
|
||||
|
@ -190,8 +197,12 @@ impl ParseState for AirTplAggregate {
|
|||
.map(|_| ())
|
||||
.transition(Toplevel(tpl.identify(id))),
|
||||
|
||||
(Toplevel(tpl), AirBind(RefIdent(id))) => {
|
||||
tpl.oi().apply_named_tpl(ctx.asg_mut(), id);
|
||||
(Toplevel(tpl), AirBind(RefIdent(name))) => {
|
||||
let tpl_oi = tpl.oi();
|
||||
let ref_oi = ctx.lookup_lexical_or_missing(name);
|
||||
|
||||
tpl_oi.apply_named_tpl(ctx.asg_mut(), ref_oi, name.span());
|
||||
|
||||
Transition(Toplevel(tpl)).incomplete()
|
||||
}
|
||||
|
||||
|
@ -246,7 +257,7 @@ impl ParseState for AirTplAggregate {
|
|||
}
|
||||
|
||||
(Toplevel(tpl), AirTpl(TplEnd(span))) => {
|
||||
tpl.close(ctx.asg_mut(), span).transition(Ready)
|
||||
tpl.close(ctx.asg_mut(), span).transition(Done)
|
||||
}
|
||||
|
||||
(Toplevel(tpl), AirTpl(TplEndRef(span))) => {
|
||||
|
@ -268,7 +279,7 @@ impl ParseState for AirTplAggregate {
|
|||
}
|
||||
|
||||
(
|
||||
Ready,
|
||||
Ready | Done,
|
||||
tok @ AirTpl(TplMetaStart(..) | TplLexeme(..) | TplMetaEnd(..)),
|
||||
) => {
|
||||
diagnostic_todo!(
|
||||
|
@ -277,16 +288,25 @@ impl ParseState for AirTplAggregate {
|
|||
)
|
||||
}
|
||||
|
||||
(st @ Ready, AirTpl(TplEnd(span) | TplEndRef(span))) => {
|
||||
Transition(st).err(AsgError::UnbalancedTpl(span))
|
||||
// If we just finished a template then this end may represent
|
||||
// the closing of a parent template.
|
||||
(Done, tok @ AirTpl(TplEnd(_) | TplEndRef(_))) => {
|
||||
Transition(Done).dead(tok)
|
||||
}
|
||||
// Otherwise we have an unbalanced close
|
||||
// (we just started parsing to receive an end token).
|
||||
(Ready, AirTpl(TplEnd(span) | TplEndRef(span))) => {
|
||||
Transition(Ready).err(AsgError::UnbalancedTpl(span))
|
||||
}
|
||||
|
||||
(st @ Ready, tok @ AirBind(..)) => Transition(st).dead(tok),
|
||||
(st @ (Ready | Done), tok @ AirBind(..)) => {
|
||||
Transition(st).dead(tok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_accepting(&self, _: &Self::Context) -> bool {
|
||||
matches!(self, Self::Ready)
|
||||
matches!(self, Self::Ready | Self::Done)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,7 +319,7 @@ impl AirTplAggregate {
|
|||
use AirTplAggregate::*;
|
||||
|
||||
match self {
|
||||
Ready => None,
|
||||
Ready | Done => None,
|
||||
Toplevel(tplst) | TplMeta(tplst, _) => Some(tplst.oi()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -448,3 +448,156 @@ fn tpl_with_param() {
|
|||
assert_eq!(params[0], Some(&Meta::Lexeme(S3.merge(S6).unwrap(), pval1)));
|
||||
assert_eq!(params[1], Some(&Meta::Required(S7.merge(S9).unwrap())));
|
||||
}
|
||||
|
||||
// A template definition nested within another creates a
|
||||
// template-defining-template.
|
||||
// The inner template and its identifier should be entirely contained within
|
||||
// the outer (parent) template so that it will expand into a template
|
||||
// definition in the context of the expansion site.
|
||||
#[test]
|
||||
fn tpl_nested() {
|
||||
let id_tpl_outer = SPair("_tpl-outer_".into(), S2);
|
||||
let id_tpl_inner = SPair("_tpl-inner_".into(), S4);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::TplStart(S1),
|
||||
Air::BindIdent(id_tpl_outer),
|
||||
|
||||
// Inner template
|
||||
Air::TplStart(S3),
|
||||
Air::BindIdent(id_tpl_inner),
|
||||
Air::TplEnd(S5),
|
||||
Air::TplEnd(S6),
|
||||
];
|
||||
|
||||
let asg = asg_from_toks(toks);
|
||||
|
||||
// The outer template should be defined globally,
|
||||
// but not the inner,
|
||||
// since it hasn't been expanded yet.
|
||||
let oi_tpl_outer = asg.expect_ident_oi::<Tpl>(id_tpl_outer);
|
||||
assert_eq!(None, asg.lookup_global(id_tpl_inner));
|
||||
assert_eq!(S1.merge(S6).unwrap(), oi_tpl_outer.resolve(&asg).span());
|
||||
|
||||
// The identifier for the inner template should be local to the outer
|
||||
// template.
|
||||
let oi_tpl_inner = oi_tpl_outer.lookup_local_linear(&asg, id_tpl_inner);
|
||||
assert_eq!(
|
||||
S3.merge(S5),
|
||||
oi_tpl_inner
|
||||
.and_then(|oi| oi.definition::<Tpl>(&asg))
|
||||
.map(|oi| oi.resolve(&asg).span())
|
||||
);
|
||||
}
|
||||
|
||||
// A template application within another template can be interpreted as
|
||||
// either applying the template as much as possible into the body of the
|
||||
// template definition,
|
||||
// or as expanding the template application into the expansion site and
|
||||
// _then_ expanding the inner template at the expansion site.
|
||||
// Both will yield equivalent results,
|
||||
// but in either case,
|
||||
// it all starts the same.
|
||||
#[test]
|
||||
fn tpl_apply_nested() {
|
||||
let id_tpl_outer = SPair("_tpl-outer_".into(), S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::TplStart(S1),
|
||||
Air::BindIdent(id_tpl_outer),
|
||||
|
||||
// Inner template application
|
||||
Air::TplStart(S3),
|
||||
Air::TplEndRef(S4),
|
||||
Air::TplEnd(S5),
|
||||
];
|
||||
|
||||
let asg = asg_from_toks(toks);
|
||||
|
||||
let oi_tpl_outer = asg.expect_ident_oi::<Tpl>(id_tpl_outer);
|
||||
assert_eq!(S1.merge(S5).unwrap(), oi_tpl_outer.resolve(&asg).span());
|
||||
|
||||
// The inner template,
|
||||
// being a template application,
|
||||
// should be a direct child of the outer template.
|
||||
let inners = oi_tpl_outer
|
||||
.edges_filtered::<Tpl>(&asg)
|
||||
.map(|oi| oi.resolve(&asg).span());
|
||||
|
||||
assert_eq!(vec![S3.merge(S4).unwrap()], inners.collect::<Vec<_>>(),);
|
||||
}
|
||||
|
||||
// Template application should resolve all the same regardless of order of
|
||||
// ref/def.
|
||||
#[test]
|
||||
fn tpl_apply_nested_missing() {
|
||||
let id_tpl_outer = SPair("_tpl-outer_".into(), S2);
|
||||
let tpl_inner = "_tpl-inner_".into();
|
||||
let id_tpl_inner = SPair(tpl_inner, S7);
|
||||
|
||||
let ref_tpl_inner_pre = SPair(tpl_inner, S4);
|
||||
let ref_tpl_inner_post = SPair(tpl_inner, S10);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::TplStart(S1),
|
||||
Air::BindIdent(id_tpl_outer),
|
||||
|
||||
// Inner template application (Missing)
|
||||
Air::TplStart(S3),
|
||||
Air::RefIdent(ref_tpl_inner_pre),
|
||||
Air::TplEndRef(S5),
|
||||
|
||||
// Define the template above
|
||||
Air::TplStart(S6),
|
||||
Air::BindIdent(id_tpl_inner),
|
||||
Air::TplEnd(S8),
|
||||
|
||||
// Apply again,
|
||||
// this time _after_ having been defined.
|
||||
Air::TplStart(S9),
|
||||
Air::RefIdent(ref_tpl_inner_post),
|
||||
Air::TplEndRef(S11),
|
||||
Air::TplEnd(S12),
|
||||
];
|
||||
|
||||
let asg = asg_from_toks(toks);
|
||||
|
||||
let oi_tpl_outer = asg.expect_ident_oi::<Tpl>(id_tpl_outer);
|
||||
assert_eq!(S1.merge(S12).unwrap(), oi_tpl_outer.resolve(&asg).span());
|
||||
|
||||
// The inner template should be contained within the outer and so not
|
||||
// globally resolvable.
|
||||
assert!(asg.lookup_global(id_tpl_inner).is_none());
|
||||
|
||||
// But it is accessible as a local on the outer template.
|
||||
let oi_tpl_inner = oi_tpl_outer
|
||||
.lookup_local_linear(&asg, id_tpl_inner)
|
||||
.expect("could not locate inner template as a local")
|
||||
.definition::<Tpl>(&asg)
|
||||
.expect("could not resolve inner template ref to Tpl");
|
||||
|
||||
// We should have two inner template applications.
|
||||
let inners = oi_tpl_outer.edges_filtered::<Tpl>(&asg).collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
vec![S9.merge(S11).unwrap(), S3.merge(S5).unwrap()],
|
||||
inners
|
||||
.iter()
|
||||
.map(|oi| oi.resolve(&asg).span())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// Each of those inner template applications should have resolved to the
|
||||
// same template,
|
||||
// despite their varying ref/def ordering.
|
||||
assert_eq!(
|
||||
vec![oi_tpl_inner, oi_tpl_inner],
|
||||
inners
|
||||
.iter()
|
||||
.flat_map(|oi| oi.edges_filtered::<Ident>(&asg))
|
||||
.filter_map(|oi| oi.definition(&asg))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1046,6 +1046,17 @@ impl ObjectIndex<Ident> {
|
|||
.map(|ident_oi| ident_oi.add_edge_to(asg, definition, None))
|
||||
}
|
||||
|
||||
/// Look up the definition that this identifier binds to,
|
||||
/// if any.
|
||||
///
|
||||
/// See [`Self::bind_definition`].
|
||||
pub fn definition<O: ObjectRelFrom<Ident> + ObjectRelatable>(
|
||||
&self,
|
||||
asg: &Asg,
|
||||
) -> Option<ObjectIndex<O>> {
|
||||
self.edges(asg).find_map(ObjectRel::narrow)
|
||||
}
|
||||
|
||||
/// Whether this identifier is bound to the object represented by `oi`.
|
||||
///
|
||||
/// To bind an identifier,
|
||||
|
|
|
@ -25,12 +25,7 @@ use super::{
|
|||
Expr, Ident, Object, ObjectIndex, ObjectIndexRelTo, ObjectRel,
|
||||
ObjectRelFrom, ObjectRelTy, ObjectRelatable, ObjectTreeRelTo,
|
||||
};
|
||||
use crate::{
|
||||
asg::Asg,
|
||||
f::Functor,
|
||||
parse::{util::SPair, Token},
|
||||
span::Span,
|
||||
};
|
||||
use crate::{asg::Asg, f::Functor, span::Span};
|
||||
|
||||
/// Template with associated name.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -73,6 +68,9 @@ object_rel! {
|
|||
// Identifiers are used for both references and identifiers that
|
||||
// will expand into an application site.
|
||||
dyn Ident,
|
||||
|
||||
// Template application.
|
||||
tree Tpl,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,9 +92,13 @@ impl ObjectIndex<Tpl> {
|
|||
/// During evaluation,
|
||||
/// this application will expand the template in place,
|
||||
/// re-binding metavariables to the context of `self`.
|
||||
pub fn apply_named_tpl(self, asg: &mut Asg, id: SPair) -> Self {
|
||||
let oi_apply = asg.lookup_global_or_missing(id);
|
||||
self.add_edge_to(asg, oi_apply, Some(id.span()))
|
||||
pub fn apply_named_tpl(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
oi_apply: ObjectIndex<Ident>,
|
||||
ref_span: Span,
|
||||
) -> Self {
|
||||
self.add_edge_to(asg, oi_apply, Some(ref_span))
|
||||
}
|
||||
|
||||
/// Directly reference this template from another object
|
||||
|
|
|
@ -241,3 +241,90 @@ fn traverses_ontological_tree_tpl_apply() {
|
|||
tree_reconstruction_report(toks),
|
||||
);
|
||||
}
|
||||
|
||||
// A template acts as a container for anything defined therein,
|
||||
// to be expanded into an application site.
|
||||
// This means that identifiers that might otherwise be bound to the package
|
||||
// need to be contained by the template,
|
||||
// and further that identifier _resolution_ must be able to occur within
|
||||
// the template,
|
||||
// e.g. to apply templates defined therein.
|
||||
#[test]
|
||||
fn traverses_ontological_tree_tpl_within_template() {
|
||||
let name_outer = "_tpl-outer_".into();
|
||||
let id_tpl_outer = SPair(name_outer, S3);
|
||||
let name_inner = "_tpl-inner_".into();
|
||||
let id_tpl_inner = SPair(name_inner, S10);
|
||||
let ref_inner_before = SPair(name_inner, S7);
|
||||
let ref_inner_after = SPair(name_inner, S13);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgStart(S1),
|
||||
TplStart(S2),
|
||||
BindIdent(id_tpl_outer),
|
||||
|
||||
// Anonymous inner template application.
|
||||
TplStart(S4),
|
||||
TplEndRef(S5), // notice the `Ref` at the end
|
||||
|
||||
// Apply above inner template,
|
||||
// _before_ definition,
|
||||
// which will begin as Missing and must be later resolved when
|
||||
// the template is defined.
|
||||
TplStart(S6),
|
||||
RefIdent(ref_inner_before), // --.
|
||||
TplEndRef(S8), // |
|
||||
// |
|
||||
// Named inner template. // |
|
||||
TplStart(S9), // /
|
||||
BindIdent(id_tpl_inner), //<-:
|
||||
TplEnd(S11), // \
|
||||
// |
|
||||
// Apply above inner template, // |
|
||||
// _after_ definition. // |
|
||||
TplStart(S12), // |
|
||||
RefIdent(ref_inner_after), // __/
|
||||
TplEndRef(S14),
|
||||
TplEnd(S15),
|
||||
PkgEnd(S16),
|
||||
];
|
||||
|
||||
// We need more concise expressions for the below table of values.
|
||||
let d = DynObjectRel::new;
|
||||
let m = |a: Span, b: Span| a.merge(b).unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
// A -|-> B | A span -|-> B span | espan | depth
|
||||
vec![//-----|-------|-----------|-----------|---------|-----------------
|
||||
(d(Root, Pkg, SU, m(S1, S16), None ), Depth(1)),
|
||||
(d(Pkg, Ident, m(S1, S16), S3, None ), Depth(2)),
|
||||
(d(Ident, Tpl, S3, m(S2, S15), None ), Depth(3)),
|
||||
(d(Tpl, Tpl, m(S2, S15), m(S4, S5), None ), Depth(4)),
|
||||
(d(Tpl, Tpl, m(S2, S15), m(S6, S8), None ), Depth(4)),
|
||||
/*cross*/ (d(Tpl, Ident, m(S6, S8), S10, Some(S7) ), Depth(5)),
|
||||
// ,--------------------------------^^^
|
||||
/* | */ (d(Tpl, Ident, m(S2, S15), S10, None ), Depth(4)),
|
||||
/* | */ (d(Ident, Tpl, S10, m(S9, S11), None ), Depth(5)),
|
||||
/* | */ (d(Tpl, Tpl, m(S2, S15), m(S12,S14), None ), Depth(4)),
|
||||
/*cross*/ (d(Tpl, Ident, m(S12,S14), S10, Some(S13)), Depth(5)),
|
||||
// | // ^^^
|
||||
// | // Note that successfully /
|
||||
// | // resolving this span --`
|
||||
// | // as S10 (Ident::Transparent) instead of S13 (which would mean
|
||||
// | // a new Ident::Missing was created) requires that we resolve
|
||||
// | // a local identifier that is rooted in the _template_ rather
|
||||
// | // than the global scope.
|
||||
// `----> Similarly,
|
||||
// resolving the former as S10 instead of S7 means that a
|
||||
// local identifier that was originally Missing is properly
|
||||
// resolved to Transparent once it was defined;
|
||||
// which asserts consistency in identifier scope regardless
|
||||
// of reference/definition order.
|
||||
// This lexical analysis is something that the XSLT-based TAME
|
||||
// was not capable of doing.
|
||||
],
|
||||
tree_reconstruction_report(toks),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -309,19 +309,21 @@ impl<'a> TreeContext<'a> {
|
|||
// Convert this into a long-hand template expansion so that we
|
||||
// do not have to deal with converting underscore-padded
|
||||
// template names back into short-hand form.
|
||||
Object::Pkg(..) => {
|
||||
Object::Pkg(..) | Object::Tpl(..) => {
|
||||
// [`Ident`]s are skipped during traversal,
|
||||
// so we'll handle it ourselves here.
|
||||
// This also gives us the opportunity to make sure that
|
||||
// we're deriving something that's actually supported by the
|
||||
// XSLT-based compiler.
|
||||
// TODO: This doesn't handle `Missing` or invalid (non-`Tpl`)
|
||||
// identifiers.
|
||||
let mut idents = oi_tpl
|
||||
.edges_filtered::<Ident>(self.asg)
|
||||
.filter(|oi| oi.is_bound_to_kind::<Tpl>(self.asg));
|
||||
// TODO: Not checking that it's a Tpl ident because we need
|
||||
// to be able to accommodate Missing identifiers until we
|
||||
// both properly handle scoping rules and support package
|
||||
// imports;
|
||||
// this is making some dangerous assumptions,
|
||||
// though they are tested.
|
||||
let ident = oi_tpl.edges_filtered::<Ident>(self.asg).last();
|
||||
|
||||
let apply_tpl = idents.next().diagnostic_expect(
|
||||
let apply_tpl = ident.diagnostic_expect(
|
||||
|| {
|
||||
vec![tpl
|
||||
.span()
|
||||
|
@ -330,19 +332,6 @@ impl<'a> TreeContext<'a> {
|
|||
"cannot derive name of template for application",
|
||||
);
|
||||
|
||||
if let Some(bad_ident) = idents.next() {
|
||||
diagnostic_panic!(
|
||||
vec![
|
||||
tpl.span().note(
|
||||
"while processing this template application"
|
||||
),
|
||||
bad_ident
|
||||
.internal_error("unexpected second identifier"),
|
||||
],
|
||||
"expected only one Ident->Tpl for template application",
|
||||
);
|
||||
}
|
||||
|
||||
self.push(attr_name(apply_tpl.resolve(self.asg).name()));
|
||||
|
||||
Some(Xirf::open(
|
||||
|
|
|
@ -125,5 +125,32 @@
|
|||
<c:product />
|
||||
</c:sum>
|
||||
</template>
|
||||
|
||||
<template name="_short-hand-nullary-outer_" />
|
||||
<apply-template name="_short-hand-nullary-outer_">
|
||||
<with-param name="@values@" value="___dsgr-d72___" />
|
||||
</apply-template>
|
||||
<template name="___dsgr-d72___">
|
||||
<template name="_short-hand-nullary-inner-dfn-inner_" />
|
||||
<apply-template name="_short-hand-nullary-inner-dfn-inner_" />
|
||||
</template>
|
||||
|
||||
<template name="_short-hand-nullary-inner-dfn-outer_" />
|
||||
<apply-template name="_short-hand-nullary-outer_">
|
||||
<with-param name="@values@" value="___dsgr-e59___" />
|
||||
</apply-template>
|
||||
<template name="___dsgr-e59___">
|
||||
<apply-template name="_short-hand-nullary-inner-dfn-outer_" />
|
||||
</template>
|
||||
|
||||
<template name="_short-hand-unary-with-values_" />
|
||||
<apply-template name="_short-hand-unary-with-values_">
|
||||
<with-param name="@foo@" value="bar" />
|
||||
<with-param name="@values@" value="___dsgr-eff___" />
|
||||
</apply-template>
|
||||
<template name="___dsgr-eff___">
|
||||
<template name="_short-hand-unary-with-values-inner_" />
|
||||
<apply-template name="_short-hand-unary-with-values-inner_" />
|
||||
</template>
|
||||
</package>
|
||||
|
||||
|
|
|
@ -125,16 +125,31 @@
|
|||
|
||||
|
||||
|
||||
|
||||
<template name="_short-hand-nullary-outer_" />
|
||||
<t:short-hand-nullary-outer>
|
||||
<template name="_short-hand-nullary-inner-dfn-inner_" />
|
||||
<t:short-hand-nullary-inner-dfn-inner />
|
||||
</t:short-hand-nullary-outer>
|
||||
|
||||
|
||||
|
||||
<template name="_short-hand-nullary-inner-dfn-outer_" />
|
||||
<t:short-hand-nullary-outer>
|
||||
<t:short-hand-nullary-inner-dfn-outer />
|
||||
</t:short-hand-nullary-outer>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<template name="_short-hand-unary-with-values_" />
|
||||
<t:short-hand-unary-with-values foo="bar">
|
||||
<template name="_short-hand-unary-with-values-inner_" />
|
||||
<t:short-hand-unary-with-values-inner />
|
||||
</t:short-hand-unary-with-values>
|
||||
<!-- TODO
|
||||
|
||||
<t:short-hand-nullary-inner>
|
||||
<t:inner-short />
|
||||
</t:short-hand-nullary-inner>
|
||||
|
||||
<t:short-hand foo="bar">
|
||||
<t:inner-short />
|
||||
</t:short-hand>
|
||||
|
||||
<rate yields="shortHandTplInExpr">
|
||||
<t:short-hand in="rate" />
|
||||
</rate>
|
||||
|
|
Loading…
Reference in New Issue