tamer: Very basic support for template application NIR -> xmli

This this a big change that's difficult to break up, and I don't have the
energy after it.

This introduces nullary template application, short- and long-form.  Note
that a body of the short form is a `@values@` argument, so that's not
supported yet.

This continues to formalize the idea of what "template application" and
"template expansion" mean in TAMER.  It makes a separate `TplApply`
unnecessary, because now application is simply a reference to a
template.  Expansion and application are one and the same: when a template
expands, it'll re-bind metavariables to the parent context.  So in a
template context, this amounts to application.

But applying a closed template will have nothing to bind, and so is
equivalent to expansion.  And since `Meta` objects are not valid outside of
a `Tpl` context, applying a non-closed template outside of another template
will be invalid.

So we get all of this with a single primitive (getting the "value" of a
template).

The expansion is conceptually like `,@` in Lisp, where we're splicing trees.

It's a mess in some spots, but I want to get this committed before I do a
little bit of cleanup.
Mike Gerwitz 2023-03-17 10:25:56 -04:00
parent aa229b827c
commit 56d92fe22b
12 changed files with 444 additions and 49 deletions

View File

@ -33,6 +33,7 @@ use super::{
use crate::{ use crate::{
fmt::{DisplayWrapper, TtQuote}, fmt::{DisplayWrapper, TtQuote},
parse::prelude::*, parse::prelude::*,
span::Span,
}; };
/// Template parser and token aggregator. /// Template parser and token aggregator.
@ -116,6 +117,29 @@ impl TplState {
fn identify(self, id: SPair) -> Self { fn identify(self, id: SPair) -> Self {
Self::Identified(self.oi(), id) Self::Identified(self.oi(), id)
} }
fn anonymous_reachable(self) -> Self {
Self::AnonymousReachable(self.oi())
}
/// Attempt to complete a template definition.
///
/// If `self` is [`Self::Dangling`],
/// then an [`AsgError::DanglingTpl`] will be returned.
///
/// This updates the span of the template to encompass the entire
/// definition,
/// even if an error occurs.
fn close(self, asg: &mut Asg, close_span: Span) -> Result<(), AsgError> {
let oi = self.oi().close(asg, close_span);
match self {
Self::Dangling(_) => {
Err(AsgError::DanglingTpl(oi.resolve(asg).span()))
}
Self::AnonymousReachable(..) | Self::Identified(..) => Ok(()),
}
}
} }
impl Display for TplState { impl Display for TplState {
@ -168,8 +192,9 @@ impl ParseState for AirTplAggregate {
.map(|_| ()) .map(|_| ())
.transition(Toplevel(oi_pkg, tpl.identify(id), expr)), .transition(Toplevel(oi_pkg, tpl.identify(id), expr)),
(Toplevel(..), AirBind(RefIdent(_))) => { (Toplevel(oi_pkg, tpl, expr), AirBind(RefIdent(id))) => {
todo!("tpl Toplevel RefIdent") tpl.oi().apply_named_tpl(asg, id);
Transition(Toplevel(oi_pkg, tpl, expr)).incomplete()
} }
( (
@ -184,25 +209,41 @@ impl ParseState for AirTplAggregate {
} }
(Toplevel(oi_pkg, tpl, _expr_done), AirTpl(TplEnd(span))) => { (Toplevel(oi_pkg, tpl, _expr_done), AirTpl(TplEnd(span))) => {
tpl.oi().close(asg, span); tpl.close(asg, span).transition(Ready(oi_pkg))
Transition(Ready(oi_pkg)).incomplete()
} }
(TplExpr(oi_pkg, tpl, expr), AirTpl(TplEnd(span))) => { (TplExpr(oi_pkg, tpl, expr), AirTpl(TplEnd(span))) => {
// TODO: duplicated with AirAggregate // TODO: duplicated with AirAggregate
match expr.is_accepting(asg) { if expr.is_accepting(asg) {
true => { tpl.close(asg, span).transition(Ready(oi_pkg))
// TODO: this is duplicated with the above } else {
tpl.oi().close(asg, span); Transition(TplExpr(oi_pkg, tpl, expr))
Transition(Ready(oi_pkg)).incomplete() .err(AsgError::InvalidTplEndContext(span))
}
false => Transition(TplExpr(oi_pkg, tpl, expr))
.err(AsgError::InvalidTplEndContext(span)),
} }
} }
(Toplevel(..) | TplExpr(..), AirTpl(TplEndRef(..))) => { (Toplevel(oi_pkg, tpl, expr_done), AirTpl(TplEndRef(span))) => {
todo!("TplEndRef") tpl.oi().expand_into(asg, oi_pkg);
Transition(Toplevel(
oi_pkg,
tpl.anonymous_reachable(),
expr_done,
))
.incomplete()
.with_lookahead(AirTpl(TplEnd(span)))
}
(TplExpr(oi_pkg, tpl, expr_done), AirTpl(TplEndRef(span))) => {
tpl.oi().expand_into(asg, oi_pkg);
Transition(TplExpr(
oi_pkg,
tpl.anonymous_reachable(),
expr_done,
))
.incomplete()
.with_lookahead(AirTpl(TplEnd(span)))
} }
( (

View File

@ -158,11 +158,14 @@ fn tpl_within_expr() {
#[test] #[test]
fn close_tpl_without_open() { fn close_tpl_without_open() {
let id_tpl = SPair("_tpl_".into(), S3);
let toks = vec![ let toks = vec![
Air::TplEnd(S1), Air::TplEnd(S1),
// RECOVERY: Try again. // RECOVERY: Try again.
Air::TplStart(S2), Air::TplStart(S2),
Air::TplEnd(S3), Air::BindIdent(id_tpl),
Air::TplEnd(S4),
]; ];
assert_eq!( assert_eq!(
@ -171,6 +174,7 @@ fn close_tpl_without_open() {
Err(ParseError::StateError(AsgError::UnbalancedTpl(S1))), Err(ParseError::StateError(AsgError::UnbalancedTpl(S1))),
// RECOVERY // RECOVERY
Ok(Parsed::Incomplete), // TplStart Ok(Parsed::Incomplete), // TplStart
Ok(Parsed::Incomplete), // BindIdent
Ok(Parsed::Incomplete), // TplEnd Ok(Parsed::Incomplete), // TplEnd
Ok(Parsed::Incomplete), // PkgEnd Ok(Parsed::Incomplete), // PkgEnd
], ],
@ -268,18 +272,21 @@ fn tpl_holds_dangling_expressions() {
#[test] #[test]
fn close_tpl_mid_open() { fn close_tpl_mid_open() {
let id_expr = SPair("expr".into(), S3); let id_tpl = SPair("_tpl_".into(), S2);
let id_expr = SPair("expr".into(), S4);
#[rustfmt::skip] #[rustfmt::skip]
let toks = vec![ let toks = vec![
Air::TplStart(S1), Air::TplStart(S1),
Air::ExprStart(ExprOp::Sum, S2), Air::BindIdent(id_tpl),
Air::BindIdent(id_expr),
Air::ExprStart(ExprOp::Sum, S3),
Air::BindIdent(id_expr),
// This is misplaced. // This is misplaced.
Air::TplEnd(S4), Air::TplEnd(S5),
// RECOVERY: Close the expression and try again. // RECOVERY: Close the expression and try again.
Air::ExprEnd(S5), Air::ExprEnd(S6),
Air::TplEnd(S6), Air::TplEnd(S7),
]; ];
assert_eq!( assert_eq!(
@ -287,10 +294,11 @@ fn close_tpl_mid_open() {
vec![ vec![
Ok(Parsed::Incomplete), // PkgStart Ok(Parsed::Incomplete), // PkgStart
Ok(Parsed::Incomplete), // TplStart Ok(Parsed::Incomplete), // TplStart
Ok(Parsed::Incomplete), // BindIdent
Ok(Parsed::Incomplete), // ExprStart Ok(Parsed::Incomplete), // ExprStart
Ok(Parsed::Incomplete), // BindIdent Ok(Parsed::Incomplete), // BindIdent
Err(ParseError::StateError( Err(ParseError::StateError(
AsgError::InvalidTplEndContext(S4)) AsgError::InvalidTplEndContext(S5))
), ),
// RECOVERY // RECOVERY
Ok(Parsed::Incomplete), // ExprEnd Ok(Parsed::Incomplete), // ExprEnd
@ -300,3 +308,75 @@ fn close_tpl_mid_open() {
parse_as_pkg_body(toks).collect::<Vec<_>>(), parse_as_pkg_body(toks).collect::<Vec<_>>(),
); );
} }
// If a template is ended with `TplEnd` and was not assigned a name,
// then it isn't reachable on the graph.
//
// ...that's technically not entirely true in a traversal sense
// (see following test),
// but the context would be all wrong.
// It _is_ true from a practical sense,
// with how NIR and AIR have been constructed at the time of writing,
// but may not be true in the future.
#[test]
fn unreachable_anonymous_tpl() {
let id_ok = SPair("_tpl_".into(), S4);
#[rustfmt::skip]
let toks = vec![
Air::TplStart(S1),
// No BindIdent
Air::TplEnd(S2),
// Recovery should ignore the above template
// (it's lost to the void)
// and allow continuing.
Air::TplStart(S3),
Air::BindIdent(id_ok),
Air::TplEnd(S5),
];
let mut sut = parse_as_pkg_body(toks);
assert_eq!(
vec![
Ok(Parsed::Incomplete), // PkgStart
Ok(Parsed::Incomplete), // TplStart
Err(ParseError::StateError(AsgError::DanglingTpl(
S1.merge(S2).unwrap()
))),
// RECOVERY
Ok(Parsed::Incomplete), // TplStart
Ok(Parsed::Incomplete), // TplBindIdent
Ok(Parsed::Incomplete), // TplEnd
Ok(Parsed::Incomplete), // PkgEnd
],
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
// Let's make sure that the template created after recovery succeeded.
asg.expect_ident_obj::<Tpl>(id_ok);
}
// Normally we cannot reference objects without an identifier using AIR
// (at the time of writing at least),
// but `TplEndRef` is an exception.
#[test]
fn anonymous_tpl_immediate_ref() {
#[rustfmt::skip]
let toks = vec![
Air::TplStart(S1),
// No BindIdent
// But ended with `TplEndRef`,
// so the missing identifier is okay.
// This would fail if it were `TplEnd`.
Air::TplEndRef(S2),
];
let mut sut = parse_as_pkg_body(toks);
assert!(sut.all(|x| x.is_ok()));
// TODO: More to come.
}

View File

@ -88,6 +88,12 @@ pub enum AsgError {
/// The span should encompass the entirety of the expression. /// The span should encompass the entirety of the expression.
DanglingExpr(Span), DanglingExpr(Span),
/// A template is not reachable by any other object.
///
/// See [`Self::DanglingExpr`] for more information on the concept of
/// dangling objects.
DanglingTpl(Span),
/// Attempted to close an expression with no corresponding opening /// Attempted to close an expression with no corresponding opening
/// delimiter. /// delimiter.
UnbalancedExpr(Span), UnbalancedExpr(Span),
@ -132,6 +138,10 @@ impl Display for AsgError {
f, f,
"dangling expression (anonymous expression has no parent)" "dangling expression (anonymous expression has no parent)"
), ),
DanglingTpl(_) => write!(
f,
"dangling template (anonymous template cannot be referenced)"
),
UnbalancedExpr(_) => write!(f, "unbalanced expression"), UnbalancedExpr(_) => write!(f, "unbalanced expression"),
UnbalancedTpl(_) => write!(f, "unbalanced template definition"), UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
InvalidExprBindContext(_) => { InvalidExprBindContext(_) => {
@ -221,6 +231,17 @@ impl Diagnostic for AsgError {
span.help(" its value cannot referenced."), span.help(" its value cannot referenced."),
], ],
DanglingTpl(span) => vec![
span.error(
"this template is unreachable and can never be used",
),
span.help(
"a template may only be anonymous if it is ephemeral ",
),
span.help(" (immediately expanded)."),
span.help("alternatively, assign this template an identifier."),
],
UnbalancedExpr(span) => { UnbalancedExpr(span) => {
vec![span.error("there is no open expression to close here")] vec![span.error("there is no open expression to close here")]
} }

View File

@ -21,7 +21,7 @@
use super::{ use super::{
Ident, Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy, Ident, Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy,
ObjectRelatable, ObjectRelatable, Tpl,
}; };
use crate::{asg::Asg, f::Functor, span::Span}; use crate::{asg::Asg, f::Functor, span::Span};
use std::fmt::Display; use std::fmt::Display;
@ -65,6 +65,9 @@ object_rel! {
/// Imported [`Ident`]s do not have edges from this package. /// Imported [`Ident`]s do not have edges from this package.
Pkg -> { Pkg -> {
tree Ident, tree Ident,
// Anonymous templates are used for expansion.
tree Tpl,
} }
} }

View File

@ -23,9 +23,9 @@ use std::fmt::Display;
use super::{ use super::{
Expr, Ident, Meta, Object, ObjectIndex, ObjectRel, ObjectRelFrom, Expr, Ident, Meta, Object, ObjectIndex, ObjectRel, ObjectRelFrom,
ObjectRelTy, ObjectRelatable, ObjectRelTo, ObjectRelTy, ObjectRelatable,
}; };
use crate::{asg::Asg, f::Functor, span::Span}; use crate::{asg::Asg, f::Functor, parse::util::SPair, span::Span};
/// Template with associated name. /// Template with associated name.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@ -68,9 +68,9 @@ object_rel! {
} }
impl ObjectIndex<Tpl> { impl ObjectIndex<Tpl> {
/// Complete a template definition. /// Attempt to complete a template definition.
/// ///
/// This simply updates the span of the template to encompass the entire /// This updates the span of the template to encompass the entire
/// definition. /// definition.
pub fn close(self, asg: &mut Asg, close_span: Span) -> Self { pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
self.map_obj(asg, |tpl| { self.map_obj(asg, |tpl| {
@ -79,4 +79,33 @@ impl ObjectIndex<Tpl> {
}) })
}) })
} }
/// Apply a named template `id` to the context of `self`.
///
/// 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_or_missing(id);
// TODO: span
self.add_edge_to(asg, oi_apply, None)
}
/// Directly reference this template from another object
/// `oi_target_parent`,
/// indicating the intent to expand the template in place.
///
/// This direct reference allows applying anonymous templates.
///
/// The term "expansion" is equivalent to the application of a closed
/// template.
/// If this template is _not_ closed,
/// it will result in an error during evaluation.
pub fn expand_into<O: ObjectRelTo<Tpl>>(
self,
asg: &mut Asg,
oi_target_parent: ObjectIndex<O>,
) -> Self {
self.add_edge_from(asg, oi_target_parent, None)
}
} }

View File

@ -38,7 +38,7 @@ use crate::{
visit::{Depth, TreeWalkRel}, visit::{Depth, TreeWalkRel},
Asg, ExprOp, Ident, Asg, ExprOp, Ident,
}, },
diagnose::Annotate, diagnose::{panic::DiagnosticPanic, Annotate},
diagnostic_panic, diagnostic_unreachable, diagnostic_panic, diagnostic_unreachable,
parse::{prelude::*, util::SPair, Transitionable}, parse::{prelude::*, util::SPair, Transitionable},
span::{Span, UNKNOWN_SPAN}, span::{Span, UNKNOWN_SPAN},
@ -194,8 +194,8 @@ impl<'a> TreeContext<'a> {
self.emit_expr(expr, paired_rel.source(), depth) self.emit_expr(expr, paired_rel.source(), depth)
} }
Object::Tpl((tpl, _)) => { Object::Tpl((tpl, oi_tpl)) => {
self.emit_template(tpl, paired_rel.source(), depth) self.emit_template(tpl, *oi_tpl, paired_rel.source(), depth)
} }
target @ Object::Meta(..) => todo!("Object::Meta: {target:?}"), target @ Object::Meta(..) => todo!("Object::Meta: {target:?}"),
@ -280,10 +280,11 @@ impl<'a> TreeContext<'a> {
)) ))
} }
/// Emit a template definition. /// Emit a template definition or application.
fn emit_template( fn emit_template(
&mut self, &mut self,
tpl: &Tpl, tpl: &Tpl,
oi_tpl: ObjectIndex<Tpl>,
src: &Object<OiPairObjectInner>, src: &Object<OiPairObjectInner>,
depth: Depth, depth: Depth,
) -> Option<Xirf> { ) -> Option<Xirf> {
@ -298,6 +299,52 @@ impl<'a> TreeContext<'a> {
)) ))
} }
// If we're not behind an Ident,
// then this is a direct template reference,
// which indicates application of a closed template
// (template expansion).
// 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(..) => {
// [`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.
let mut idents = oi_tpl.edges_filtered::<Ident>(self.asg);
let apply_tpl = idents.next().diagnostic_expect(
|| {
vec![tpl
.span()
.internal_error("missing target Tpl Ident")]
},
"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 for template application",
);
}
self.push(attr_name(apply_tpl.resolve(self.asg).name()));
Some(Xirf::open(
QN_APPLY_TEMPLATE,
OpenSpan::without_name_span(tpl.span()),
depth,
))
}
_ => todo!("emit_template: {src:?}"), _ => todo!("emit_template: {src:?}"),
} }
} }

View File

@ -203,6 +203,10 @@ pub enum NirEntity {
Tpl, Tpl,
/// Template parameter (metavariable). /// Template parameter (metavariable).
TplParam, TplParam,
/// Full application and expansion of the template identified by the
/// provided name.
TplApply(Option<QName>),
} }
impl NirEntity { impl NirEntity {
@ -234,6 +238,14 @@ impl Display for NirEntity {
Tpl => write!(f, "template"), Tpl => write!(f, "template"),
TplParam => write!(f, "template param (metavariable)"), TplParam => write!(f, "template param (metavariable)"),
TplApply(None) => {
write!(f, "full template application and expansion")
}
TplApply(Some(qname)) => write!(
f,
"full template application and expansion of {}",
TtQuote::wrap(qname.local_name())
),
} }
} }
} }

View File

@ -22,11 +22,15 @@
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use crate::{ use crate::{
asg::{air::Air, ExprOp}, asg::air::Air, diagnose::Diagnostic, parse::prelude::*, span::UNKNOWN_SPAN,
diagnose::Diagnostic, };
// These are also used by the `test` module which imports `super`.
#[cfg(feature = "wip-nir-to-air")]
use crate::{
asg::ExprOp,
nir::NirEntity, nir::NirEntity,
parse::prelude::*, sym::{GlobalSymbolIntern, GlobalSymbolResolve},
span::UNKNOWN_SPAN,
}; };
use super::Nir; use super::Nir;
@ -47,25 +51,39 @@ impl Display for NirToAir {
} }
} }
type QueuedObj = Option<Air>;
impl ParseState for NirToAir { impl ParseState for NirToAir {
type Token = Nir; type Token = Nir;
type Object = Air; type Object = Air;
type Error = NirToAirError; type Error = NirToAirError;
type Context = QueuedObj;
#[cfg(not(feature = "wip-nir-to-air"))]
fn parse_token( fn parse_token(
self, self,
tok: Self::Token, tok: Self::Token,
_: NoContext, _queue: &mut Self::Context,
) -> TransitionResult<Self::Super> { ) -> TransitionResult<Self::Super> {
use NirToAir::*; use NirToAir::*;
#[cfg(not(feature = "wip-nir-to-air"))] let _ = tok; // prevent `unused_variables` warning
{ return Transition(Ready).ok(Air::Todo(UNKNOWN_SPAN));
let _ = tok; // prevent `unused_variables` warning }
return Transition(Ready).ok(Air::Todo(UNKNOWN_SPAN));
#[cfg(feature = "wip-nir-to-air")]
fn parse_token(
self,
tok: Self::Token,
queue: &mut Self::Context,
) -> TransitionResult<Self::Super> {
use NirToAir::*;
// Single-item "queue".
if let Some(obj) = queue.take() {
return Transition(Ready).ok(obj).with_lookahead(tok);
} }
#[allow(unreachable_code)] // due to wip-nir-to-air
match (self, tok) { match (self, tok) {
(Ready, Nir::Open(NirEntity::Package, span)) => { (Ready, Nir::Open(NirEntity::Package, span)) => {
Transition(Ready).ok(Air::PkgStart(span)) Transition(Ready).ok(Air::PkgStart(span))
@ -97,11 +115,37 @@ impl ParseState for NirToAir {
(Ready, Nir::Open(NirEntity::Tpl, span)) => { (Ready, Nir::Open(NirEntity::Tpl, span)) => {
Transition(Ready).ok(Air::TplStart(span)) Transition(Ready).ok(Air::TplStart(span))
} }
(Ready, Nir::Close(NirEntity::Tpl, span)) => { (Ready, Nir::Close(NirEntity::Tpl, span)) => {
Transition(Ready).ok(Air::TplEnd(span)) Transition(Ready).ok(Air::TplEnd(span))
} }
(Ready, Nir::Open(NirEntity::TplApply(None), span)) => {
Transition(Ready).ok(Air::TplStart(span))
}
// Short-hand template application contains the name of the
// template _without_ the underscore padding as the local part
// of the QName.
//
// Template application will create an anonymous template,
// apply it,
// and then expand it.
(Ready, Nir::Open(NirEntity::TplApply(Some(qname)), span)) => {
// TODO: Determine whether caching these has any notable
// benefit over repeated heap allocations,
// comparing packages with very few applications and
// packages with thousands
// (we'd still have to hit the heap for the cache).
let tpl_name =
format!("_{}_", qname.local_name().lookup_str()).intern();
queue.replace(Air::RefIdent(SPair(tpl_name, span)));
Transition(Ready).ok(Air::TplStart(span))
}
(Ready, Nir::Close(NirEntity::TplApply(_), span)) => {
Transition(Ready).ok(Air::TplEndRef(span))
}
( (
Ready, Ready,
Nir::Close( Nir::Close(
@ -120,12 +164,14 @@ impl ParseState for NirToAir {
(Ready, Nir::BindIdent(spair)) => { (Ready, Nir::BindIdent(spair)) => {
Transition(Ready).ok(Air::BindIdent(spair)) Transition(Ready).ok(Air::BindIdent(spair))
} }
(Ready, Nir::Ref(spair)) => {
Transition(Ready).ok(Air::RefIdent(spair))
}
( (
Ready, Ready,
Nir::Todo Nir::Todo
| Nir::TodoAttr(..) | Nir::TodoAttr(..)
| Nir::Ref(..)
| Nir::Desc(..) | Nir::Desc(..)
| Nir::Text(_) | Nir::Text(_)
| Nir::Open(NirEntity::TplParam, _) | Nir::Open(NirEntity::TplParam, _)

View File

@ -18,7 +18,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
use super::*; use super::*;
use crate::{parse::util::SPair, span::dummy::*}; use crate::{convert::ExpectInto, parse::util::SPair, span::dummy::*};
type Sut = NirToAir; type Sut = NirToAir;
@ -137,3 +137,62 @@ fn tpl_with_name() {
Sut::parse(toks.into_iter()).collect(), Sut::parse(toks.into_iter()).collect(),
); );
} }
// This is the form everyone uses.
// It applies a template much more concisely and without the underscore
// padding,
// making it look much like a language primitive
// (with the exception of the namespace prefix).
#[test]
fn short_hand_tpl_apply() {
// Shorthand converts `t:tpl-name` into `_tpl-name_`.
let qname = ("t", "tpl-name").unwrap_into();
let name = SPair("_tpl-name_".into(), S1);
let toks = vec![
Nir::Open(NirEntity::TplApply(Some(qname)), S1),
Nir::Close(NirEntity::TplApply(None), S2),
];
#[rustfmt::skip]
assert_eq!(
Ok(vec![
O(Air::TplStart(S1)),
// The span associated with the name of the template to be
// applied is the span of the entire QName from NIR.
// The reason for this is that `t:foo` is transformed into
// `_foo_`,
// and so the `t:` is a necessary part of the
// representation of the name of the template;
// `foo` is not in itself a valid template name at the
// time of writing.
O(Air::RefIdent(name)),
O(Air::TplEndRef(S2)),
]),
Sut::parse(toks.into_iter()).collect(),
);
}
// Long form takes the actual underscore-padded template name without any
// additional processing.
#[test]
fn apply_template_long_form() {
let name = SPair("_tpl-name_".into(), S2);
#[rustfmt::skip]
let toks = vec![
Nir::Open(NirEntity::TplApply(None), S1),
Nir::Ref(name),
Nir::Close(NirEntity::TplApply(None), S3),
];
#[rustfmt::skip]
assert_eq!(
Ok(vec![
O(Air::TplStart(S1)),
O(Air::RefIdent(name)),
O(Air::TplEndRef(S3)),
]),
Sut::parse(toks.into_iter()).collect(),
);
}

View File

@ -1678,10 +1678,14 @@ ele_parse! {
/// in favor of a transition to [`TplApplyShort`], /// in favor of a transition to [`TplApplyShort`],
/// but this is still needed to support dynamic template application /// but this is still needed to support dynamic template application
/// (templates whose names are derived from other template inputs). /// (templates whose names are derived from other template inputs).
ApplyTemplate := QN_APPLY_TEMPLATE { ApplyTemplate := QN_APPLY_TEMPLATE(_, ospan) {
@ {} => Todo, @ {
QN_NAME => Ref,
} => Nir::Open(NirEntity::TplApply(None), ospan.into()),
/(cspan) => Nir::Close(NirEntity::TplApply(None), cspan.into()),
// TODO // TODO: This is wrong, we just need something here for now.
AnyStmtOrExpr,
}; };
/// Short-hand template application. /// Short-hand template application.
@ -1691,8 +1695,9 @@ ele_parse! {
/// and where the body of this application is the `@values@` /// and where the body of this application is the `@values@`
/// template argument. /// template argument.
/// See [`ApplyTemplate`] for more information. /// See [`ApplyTemplate`] for more information.
TplApplyShort := NS_T { TplApplyShort := NS_T(qname, ospan) {
@ {} => Todo, @ {} => Nir::Open(NirEntity::TplApply(Some(qname)), ospan.into()),
/(cspan) => Nir::Close(NirEntity::TplApply(None), cspan.into()),
// Streaming attribute parsing; // Streaming attribute parsing;
// this takes precedence over any attribute parsing above // this takes precedence over any attribute parsing above

View File

@ -50,11 +50,22 @@
<rate yields="tplStaticMix" /> <rate yields="tplStaticMix" />
<c:sum> <c:sum>
<c:product /> <c:product />
</c:sum> </c:sum>
</template> </template>
<apply-template name="_short-hand-nullary_" />
</package> </package>

View File

@ -58,5 +58,46 @@
<c:product /> <c:product />
</c:sum> </c:sum>
</template> </template>
Short-hand template application.
These get expanding into the long form so that we don't have to translate
back and forth between the underscore-padded strings.
The fixpoint test will further verify that TAMER also recognizes the long
`apply-template` form,
asserting their equivalency.
<t:short-hand-nullary />
<!-- TODO
<t:short-hand-nullary-body>
<c:sum />
</t:short-hand-nullary-body>
<t:short-hand-nullary-inner>
<t:inner-short />
</t:short-hand-nullary-inner>
<t:short-hand foo="bar" />
<t:short-hand foo="bar">
<c:sum />
</t:short-hand>
<t:short-hand foo="bar">
<t:inner-short />
</t:short-hand>
<rate yields="shortHandTplInExpr">
<t:short-hand in="rate" />
</rate>
<template name="_tpl-with-short-hand-inner_">
<t:short-hand />
<c:sum>
<t:short-hand in="sum" />
</c:sum>
</template>
-->
</package> </package>