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.
parent
aa229b827c
commit
56d92fe22b
|
@ -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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
@ -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.
|
||||||
|
}
|
||||||
|
|
|
@ -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")]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, _)
|
||||||
|
|
|
@ -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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue