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.main
parent
aa229b827c
commit
9d50157f8e
|
@ -33,6 +33,7 @@ use super::{
|
|||
use crate::{
|
||||
fmt::{DisplayWrapper, TtQuote},
|
||||
parse::prelude::*,
|
||||
span::Span,
|
||||
};
|
||||
|
||||
/// Template parser and token aggregator.
|
||||
|
@ -116,6 +117,29 @@ impl TplState {
|
|||
fn identify(self, id: SPair) -> Self {
|
||||
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 {
|
||||
|
@ -168,8 +192,9 @@ impl ParseState for AirTplAggregate {
|
|||
.map(|_| ())
|
||||
.transition(Toplevel(oi_pkg, tpl.identify(id), expr)),
|
||||
|
||||
(Toplevel(..), AirBind(RefIdent(_))) => {
|
||||
todo!("tpl Toplevel RefIdent")
|
||||
(Toplevel(oi_pkg, tpl, expr), AirBind(RefIdent(id))) => {
|
||||
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))) => {
|
||||
tpl.oi().close(asg, span);
|
||||
Transition(Ready(oi_pkg)).incomplete()
|
||||
tpl.close(asg, span).transition(Ready(oi_pkg))
|
||||
}
|
||||
|
||||
(TplExpr(oi_pkg, tpl, expr), AirTpl(TplEnd(span))) => {
|
||||
// TODO: duplicated with AirAggregate
|
||||
match expr.is_accepting(asg) {
|
||||
true => {
|
||||
// TODO: this is duplicated with the above
|
||||
tpl.oi().close(asg, span);
|
||||
Transition(Ready(oi_pkg)).incomplete()
|
||||
}
|
||||
false => Transition(TplExpr(oi_pkg, tpl, expr))
|
||||
.err(AsgError::InvalidTplEndContext(span)),
|
||||
if expr.is_accepting(asg) {
|
||||
tpl.close(asg, span).transition(Ready(oi_pkg))
|
||||
} else {
|
||||
Transition(TplExpr(oi_pkg, tpl, expr))
|
||||
.err(AsgError::InvalidTplEndContext(span))
|
||||
}
|
||||
}
|
||||
|
||||
(Toplevel(..) | TplExpr(..), AirTpl(TplEndRef(..))) => {
|
||||
todo!("TplEndRef")
|
||||
(Toplevel(oi_pkg, tpl, expr_done), AirTpl(TplEndRef(span))) => {
|
||||
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)))
|
||||
}
|
||||
|
||||
(
|
||||
|
|
|
@ -158,11 +158,14 @@ fn tpl_within_expr() {
|
|||
|
||||
#[test]
|
||||
fn close_tpl_without_open() {
|
||||
let id_tpl = SPair("_tpl_".into(), S3);
|
||||
|
||||
let toks = vec![
|
||||
Air::TplEnd(S1),
|
||||
// RECOVERY: Try again.
|
||||
Air::TplStart(S2),
|
||||
Air::TplEnd(S3),
|
||||
Air::BindIdent(id_tpl),
|
||||
Air::TplEnd(S4),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
|
@ -171,6 +174,7 @@ fn close_tpl_without_open() {
|
|||
Err(ParseError::StateError(AsgError::UnbalancedTpl(S1))),
|
||||
// RECOVERY
|
||||
Ok(Parsed::Incomplete), // TplStart
|
||||
Ok(Parsed::Incomplete), // BindIdent
|
||||
Ok(Parsed::Incomplete), // TplEnd
|
||||
Ok(Parsed::Incomplete), // PkgEnd
|
||||
],
|
||||
|
@ -268,18 +272,21 @@ fn tpl_holds_dangling_expressions() {
|
|||
|
||||
#[test]
|
||||
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]
|
||||
let toks = vec![
|
||||
Air::TplStart(S1),
|
||||
Air::ExprStart(ExprOp::Sum, S2),
|
||||
Air::BindIdent(id_expr),
|
||||
Air::BindIdent(id_tpl),
|
||||
|
||||
Air::ExprStart(ExprOp::Sum, S3),
|
||||
Air::BindIdent(id_expr),
|
||||
// This is misplaced.
|
||||
Air::TplEnd(S4),
|
||||
Air::TplEnd(S5),
|
||||
// RECOVERY: Close the expression and try again.
|
||||
Air::ExprEnd(S5),
|
||||
Air::TplEnd(S6),
|
||||
Air::ExprEnd(S6),
|
||||
Air::TplEnd(S7),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
|
@ -287,10 +294,11 @@ fn close_tpl_mid_open() {
|
|||
vec![
|
||||
Ok(Parsed::Incomplete), // PkgStart
|
||||
Ok(Parsed::Incomplete), // TplStart
|
||||
Ok(Parsed::Incomplete), // BindIdent
|
||||
Ok(Parsed::Incomplete), // ExprStart
|
||||
Ok(Parsed::Incomplete), // BindIdent
|
||||
Err(ParseError::StateError(
|
||||
AsgError::InvalidTplEndContext(S4))
|
||||
AsgError::InvalidTplEndContext(S5))
|
||||
),
|
||||
// RECOVERY
|
||||
Ok(Parsed::Incomplete), // ExprEnd
|
||||
|
@ -300,3 +308,75 @@ fn close_tpl_mid_open() {
|
|||
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.
|
||||
}
|
||||
|
|
|
@ -88,6 +88,12 @@ pub enum AsgError {
|
|||
/// The span should encompass the entirety of the expression.
|
||||
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
|
||||
/// delimiter.
|
||||
UnbalancedExpr(Span),
|
||||
|
@ -132,6 +138,10 @@ impl Display for AsgError {
|
|||
f,
|
||||
"dangling expression (anonymous expression has no parent)"
|
||||
),
|
||||
DanglingTpl(_) => write!(
|
||||
f,
|
||||
"dangling template (anonymous template cannot be referenced)"
|
||||
),
|
||||
UnbalancedExpr(_) => write!(f, "unbalanced expression"),
|
||||
UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
|
||||
InvalidExprBindContext(_) => {
|
||||
|
@ -221,6 +231,17 @@ impl Diagnostic for AsgError {
|
|||
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) => {
|
||||
vec![span.error("there is no open expression to close here")]
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
use super::{
|
||||
Ident, Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy,
|
||||
ObjectRelatable,
|
||||
ObjectRelatable, Tpl,
|
||||
};
|
||||
use crate::{asg::Asg, f::Functor, span::Span};
|
||||
use std::fmt::Display;
|
||||
|
@ -65,6 +65,9 @@ object_rel! {
|
|||
/// Imported [`Ident`]s do not have edges from this package.
|
||||
Pkg -> {
|
||||
tree Ident,
|
||||
|
||||
// Anonymous templates are used for expansion.
|
||||
tree Tpl,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,9 @@ use std::fmt::Display;
|
|||
|
||||
use super::{
|
||||
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.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -68,9 +68,9 @@ object_rel! {
|
|||
}
|
||||
|
||||
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.
|
||||
pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ use crate::{
|
|||
visit::{Depth, TreeWalkRel},
|
||||
Asg, ExprOp, Ident,
|
||||
},
|
||||
diagnose::Annotate,
|
||||
diagnose::{panic::DiagnosticPanic, Annotate},
|
||||
diagnostic_panic, diagnostic_unreachable,
|
||||
parse::{prelude::*, util::SPair, Transitionable},
|
||||
span::{Span, UNKNOWN_SPAN},
|
||||
|
@ -194,8 +194,8 @@ impl<'a> TreeContext<'a> {
|
|||
self.emit_expr(expr, paired_rel.source(), depth)
|
||||
}
|
||||
|
||||
Object::Tpl((tpl, _)) => {
|
||||
self.emit_template(tpl, paired_rel.source(), depth)
|
||||
Object::Tpl((tpl, oi_tpl)) => {
|
||||
self.emit_template(tpl, *oi_tpl, paired_rel.source(), depth)
|
||||
}
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
tpl: &Tpl,
|
||||
oi_tpl: ObjectIndex<Tpl>,
|
||||
src: &Object<OiPairObjectInner>,
|
||||
depth: Depth,
|
||||
) -> 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:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,6 +203,10 @@ pub enum NirEntity {
|
|||
Tpl,
|
||||
/// Template parameter (metavariable).
|
||||
TplParam,
|
||||
|
||||
/// Full application and expansion of the template identified by the
|
||||
/// provided name.
|
||||
TplApply(Option<QName>),
|
||||
}
|
||||
|
||||
impl NirEntity {
|
||||
|
@ -234,6 +238,14 @@ impl Display for NirEntity {
|
|||
|
||||
Tpl => write!(f, "template"),
|
||||
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())
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,15 @@
|
|||
use std::{error::Error, fmt::Display};
|
||||
|
||||
use crate::{
|
||||
asg::{air::Air, ExprOp},
|
||||
diagnose::Diagnostic,
|
||||
asg::air::Air, diagnose::Diagnostic, parse::prelude::*, span::UNKNOWN_SPAN,
|
||||
};
|
||||
|
||||
// These are also used by the `test` module which imports `super`.
|
||||
#[cfg(feature = "wip-nir-to-air")]
|
||||
use crate::{
|
||||
asg::ExprOp,
|
||||
nir::NirEntity,
|
||||
parse::prelude::*,
|
||||
span::UNKNOWN_SPAN,
|
||||
sym::{GlobalSymbolIntern, GlobalSymbolResolve},
|
||||
};
|
||||
|
||||
use super::Nir;
|
||||
|
@ -47,25 +51,39 @@ impl Display for NirToAir {
|
|||
}
|
||||
}
|
||||
|
||||
type QueuedObj = Option<Air>;
|
||||
|
||||
impl ParseState for NirToAir {
|
||||
type Token = Nir;
|
||||
type Object = Air;
|
||||
type Error = NirToAirError;
|
||||
type Context = QueuedObj;
|
||||
|
||||
#[cfg(not(feature = "wip-nir-to-air"))]
|
||||
fn parse_token(
|
||||
self,
|
||||
tok: Self::Token,
|
||||
_: NoContext,
|
||||
_queue: &mut Self::Context,
|
||||
) -> TransitionResult<Self::Super> {
|
||||
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
|
||||
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) {
|
||||
(Ready, Nir::Open(NirEntity::Package, span)) => {
|
||||
Transition(Ready).ok(Air::PkgStart(span))
|
||||
|
@ -97,11 +115,37 @@ impl ParseState for NirToAir {
|
|||
(Ready, Nir::Open(NirEntity::Tpl, span)) => {
|
||||
Transition(Ready).ok(Air::TplStart(span))
|
||||
}
|
||||
|
||||
(Ready, Nir::Close(NirEntity::Tpl, 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,
|
||||
Nir::Close(
|
||||
|
@ -120,12 +164,14 @@ impl ParseState for NirToAir {
|
|||
(Ready, Nir::BindIdent(spair)) => {
|
||||
Transition(Ready).ok(Air::BindIdent(spair))
|
||||
}
|
||||
(Ready, Nir::Ref(spair)) => {
|
||||
Transition(Ready).ok(Air::RefIdent(spair))
|
||||
}
|
||||
|
||||
(
|
||||
Ready,
|
||||
Nir::Todo
|
||||
| Nir::TodoAttr(..)
|
||||
| Nir::Ref(..)
|
||||
| Nir::Desc(..)
|
||||
| Nir::Text(_)
|
||||
| Nir::Open(NirEntity::TplParam, _)
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use crate::{parse::util::SPair, span::dummy::*};
|
||||
use crate::{convert::ExpectInto, parse::util::SPair, span::dummy::*};
|
||||
|
||||
type Sut = NirToAir;
|
||||
|
||||
|
@ -137,3 +137,62 @@ fn tpl_with_name() {
|
|||
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(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1678,10 +1678,14 @@ ele_parse! {
|
|||
/// in favor of a transition to [`TplApplyShort`],
|
||||
/// but this is still needed to support dynamic template application
|
||||
/// (templates whose names are derived from other template inputs).
|
||||
ApplyTemplate := QN_APPLY_TEMPLATE {
|
||||
@ {} => Todo,
|
||||
ApplyTemplate := QN_APPLY_TEMPLATE(_, ospan) {
|
||||
@ {
|
||||
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.
|
||||
|
@ -1691,8 +1695,9 @@ ele_parse! {
|
|||
/// and where the body of this application is the `@values@`
|
||||
/// template argument.
|
||||
/// See [`ApplyTemplate`] for more information.
|
||||
TplApplyShort := NS_T {
|
||||
@ {} => Todo,
|
||||
TplApplyShort := NS_T(qname, ospan) {
|
||||
@ {} => Nir::Open(NirEntity::TplApply(Some(qname)), ospan.into()),
|
||||
/(cspan) => Nir::Close(NirEntity::TplApply(None), cspan.into()),
|
||||
|
||||
// Streaming attribute parsing;
|
||||
// this takes precedence over any attribute parsing above
|
||||
|
|
|
@ -50,11 +50,22 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
<rate yields="tplStaticMix" />
|
||||
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<apply-template name="_short-hand-nullary_" />
|
||||
</package>
|
||||
|
||||
|
|
|
@ -58,5 +58,46 @@
|
|||
<c:product />
|
||||
</c:sum>
|
||||
</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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue