tamer: Initial template/param support through xmli

This introduces template/param and regenerates it in the xmli output.  Note
that this does not check that applications reference known params; that's a
later phase.

DEV-13163
main
Mike Gerwitz 2023-06-14 16:38:05 -04:00
parent 9887abd037
commit d10bf00f5d
11 changed files with 149 additions and 21 deletions

View File

@ -229,7 +229,8 @@ impl ParseState for AirAggregate {
} }
(PkgMeta(meta), AirMeta(mtok)) => ctx.proxy(meta, mtok), (PkgMeta(meta), AirMeta(mtok)) => ctx.proxy(meta, mtok),
(PkgMeta(meta), AirBind(mtok)) => ctx.proxy(meta, mtok), (PkgMeta(meta), AirBind(mtok)) => ctx.proxy(meta, mtok),
(PkgMeta(meta), tok @ (AirExpr(..) | AirTpl(..) | AirDoc(..))) => { (PkgMeta(meta), AirDoc(mtok)) => ctx.proxy(meta, mtok),
(PkgMeta(meta), tok @ (AirExpr(..) | AirTpl(..))) => {
ctx.try_ret_with_lookahead(meta, tok) ctx.try_ret_with_lookahead(meta, tok)
} }

View File

@ -854,7 +854,7 @@ sum_ir! {
pub sum enum AirBindableTpl = AirTpl | AirBind | AirDoc; pub sum enum AirBindableTpl = AirTpl | AirBind | AirDoc;
/// Tokens that may be used to define metavariables. /// Tokens that may be used to define metavariables.
pub sum enum AirBindableMeta = AirMeta | AirBind; pub sum enum AirBindableMeta = AirMeta | AirBind | AirDoc;
} }
impl AirBind { impl AirBind {

View File

@ -64,7 +64,7 @@ impl ParseState for AirMetaAggregate {
tok: Self::Token, tok: Self::Token,
ctx: &mut Self::Context, ctx: &mut Self::Context,
) -> TransitionResult<Self::Super> { ) -> TransitionResult<Self::Super> {
use super::ir::{AirBind::*, AirMeta::*}; use super::ir::{AirBind::*, AirDoc::*, AirMeta::*};
use AirBindableMeta::*; use AirBindableMeta::*;
use AirMetaAggregate::*; use AirMetaAggregate::*;
@ -91,6 +91,19 @@ impl ParseState for AirMetaAggregate {
.map(|_| ()) .map(|_| ())
.transition(TplMeta(oi_meta)), .transition(TplMeta(oi_meta)),
(TplMeta(oi_meta), AirDoc(DocIndepClause(clause))) => {
oi_meta.desc_short(ctx.asg_mut(), clause);
Transition(TplMeta(oi_meta)).incomplete()
}
(TplMeta(..), tok @ AirDoc(DocText(..))) => {
diagnostic_todo!(
vec![tok.note("this token")],
"AirDoc in metavar context \
(is this something we want to support?)"
)
}
(TplMeta(..), tok @ AirBind(RefIdent(..))) => { (TplMeta(..), tok @ AirBind(RefIdent(..))) => {
diagnostic_todo!( diagnostic_todo!(
vec![tok.note("this token")], vec![tok.note("this token")],
@ -119,8 +132,10 @@ impl ParseState for AirMetaAggregate {
) )
} }
// Maybe the bind can be handled by the parent frame. // Maybe the token can be handled by the parent frame.
(Ready, tok @ AirBind(..)) => Transition(Ready).dead(tok), (Ready, tok @ (AirBind(..) | AirDoc(..))) => {
Transition(Ready).dead(tok)
}
} }
} }

View File

@ -462,6 +462,7 @@ fn tpl_with_param() {
let id_param1 = SPair("@param1@".into(), S4); let id_param1 = SPair("@param1@".into(), S4);
let pval1 = SPair("value1".into(), S5); let pval1 = SPair("value1".into(), S5);
let id_param2 = SPair("@param2@".into(), S8); let id_param2 = SPair("@param2@".into(), S8);
let param_desc = SPair("param desc".into(), S9);
#[rustfmt::skip] #[rustfmt::skip]
let toks = vec![ let toks = vec![
@ -477,8 +478,9 @@ fn tpl_with_param() {
// Required metavariable (no value). // Required metavariable (no value).
Air::MetaStart(S7), Air::MetaStart(S7),
Air::BindIdent(id_param2), Air::BindIdent(id_param2),
Air::MetaEnd(S9), Air::DocIndepClause(param_desc),
Air::TplEnd(S10), Air::MetaEnd(S10),
Air::TplEnd(S11),
]; ];
let ctx = air_ctx_from_pkg_body_toks(toks); let ctx = air_ctx_from_pkg_body_toks(toks);
@ -494,12 +496,28 @@ fn tpl_with_param() {
oi_tpl oi_tpl
.lookup_local_linear(&asg, *id) .lookup_local_linear(&asg, *id)
.and_then(|oi| oi.edges_filtered::<Meta>(&asg).next()) .and_then(|oi| oi.edges_filtered::<Meta>(&asg).next())
.map(ObjectIndex::cresolve(&asg)) .map(|oi| (oi, oi.resolve(&asg)))
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(params[0], Some(&Meta::Lexeme(S3.merge(S6).unwrap(), pval1))); assert_eq!(
assert_eq!(params[1], Some(&Meta::Required(S7.merge(S9).unwrap()))); params[0].unwrap().1,
&Meta::Lexeme(S3.merge(S6).unwrap(), pval1)
);
assert_eq!(
params[1].unwrap().1,
&Meta::Required(S7.merge(S10).unwrap())
);
// The second param should have a description.
let doc = params[1]
.unwrap()
.0
.edges_filtered::<Doc>(&asg)
.last()
.map(ObjectIndex::cresolve(&asg));
assert_eq!(doc, Some(&Doc::new_indep_clause(param_desc)));
} }
// A template definition nested within another creates a // A template definition nested within another creates a

View File

@ -24,7 +24,7 @@
//! have historically been a feature of the template system. //! have historically been a feature of the template system.
//! The canonical metavariable is the template parameter. //! The canonical metavariable is the template parameter.
use super::{prelude::*, Ident}; use super::{prelude::*, Doc, Ident};
use crate::{ use crate::{
diagnose::Annotate, diagnose::Annotate,
diagnostic_todo, diagnostic_todo,
@ -133,6 +133,9 @@ object_rel! {
Meta -> { Meta -> {
tree Meta, // TODO: do we need tree? tree Meta, // TODO: do we need tree?
cross Ident, cross Ident,
// e.g. template paramater description.
tree Doc,
} }
} }

View File

@ -154,6 +154,10 @@ type TokenStack = ArrayVec<Xirf, TOK_STACK_SIZE>;
pub struct TreeContext<'a> { pub struct TreeContext<'a> {
stack: TokenStack, stack: TokenStack,
asg: &'a Asg, asg: &'a Asg,
/// Whether the most recently encountered template has been interpreted
/// as an application.
tpl_apply: Option<ObjectIndex<Tpl>>,
} }
impl<'a> TreeContext<'a> { impl<'a> TreeContext<'a> {
@ -210,9 +214,10 @@ impl<'a> TreeContext<'a> {
self.emit_template(tpl, *oi_tpl, paired_rel.source(), depth) self.emit_template(tpl, *oi_tpl, paired_rel.source(), depth)
} }
Object::Meta((meta, oi_meta)) => { Object::Meta((meta, oi_meta)) => match self.tpl_apply {
self.emit_tpl_arg(meta, *oi_meta, depth) Some(_) => self.emit_tpl_arg(meta, *oi_meta, depth),
} None => self.emit_tpl_param(meta, *oi_meta, depth),
},
Object::Doc((doc, oi_doc)) => { Object::Doc((doc, oi_doc)) => {
self.emit_doc(doc, *oi_doc, paired_rel.source(), depth) self.emit_doc(doc, *oi_doc, paired_rel.source(), depth)
@ -387,6 +392,7 @@ impl<'a> TreeContext<'a> {
) -> Option<Xirf> { ) -> Option<Xirf> {
match src { match src {
Object::Ident((ident, _)) => { Object::Ident((ident, _)) => {
self.tpl_apply = None;
self.push(attr_name(ident.name())); self.push(attr_name(ident.name()));
Some(Xirf::open( Some(Xirf::open(
@ -404,6 +410,12 @@ impl<'a> TreeContext<'a> {
// do not have to deal with converting underscore-padded // do not have to deal with converting underscore-padded
// template names back into short-hand form. // template names back into short-hand form.
Object::Pkg(..) | Object::Tpl(..) | Object::Expr(..) => { Object::Pkg(..) | Object::Tpl(..) | Object::Expr(..) => {
// This really ought to be a state transition;
// this is a sheer act of laziness.
// If we introduce states for other things,
// let's convert this as well.
self.tpl_apply = Some(oi_tpl);
// [`Ident`]s are skipped during traversal, // [`Ident`]s are skipped during traversal,
// so we'll handle it ourselves here. // so we'll handle it ourselves here.
// This also gives us the opportunity to make sure that // This also gives us the opportunity to make sure that
@ -444,6 +456,33 @@ impl<'a> TreeContext<'a> {
} }
} }
/// Emit a metavariable as a template parameter.
///
/// For the parent template,
/// see [`Self::emit_template`].
fn emit_tpl_param(
&mut self,
meta: &Meta,
oi_meta: ObjectIndex<Meta>,
depth: Depth,
) -> Option<Xirf> {
let pname =
oi_meta
.ident(self.asg)
.map(Ident::name)
.diagnostic_unwrap(|| {
vec![meta.internal_error("missing param name")]
});
self.push(attr_name(pname));
Some(Xirf::open(
QN_PARAM,
OpenSpan::without_name_span(meta.span()),
depth,
))
}
/// Emit a long-form template argument. /// Emit a long-form template argument.
/// ///
/// For the parent template application, /// For the parent template application,
@ -501,6 +540,13 @@ impl<'a> TreeContext<'a> {
Some(attr_desc(*desc)) Some(attr_desc(*desc))
} }
// template/param/@desc
(Object::Meta(_), Doc::IndepClause(desc))
if self.tpl_apply.is_none() =>
{
Some(attr_desc(*desc))
}
(_, Doc::Text(_text)) => { (_, Doc::Text(_text)) => {
// TODO: This isn't utilized by the XSLT parser and // TODO: This isn't utilized by the XSLT parser and
// `xmllint` for system tests does not format with mixed // `xmllint` for system tests does not format with mixed
@ -564,6 +610,7 @@ impl<'a> From<&'a Asg> for TreeContext<'a> {
TreeContext { TreeContext {
stack: Default::default(), stack: Default::default(),
asg, asg,
tpl_apply: None,
} }
} }
} }

View File

@ -263,8 +263,7 @@ impl ParseState for NirToAir {
// Some of these will be permitted in the future. // Some of these will be permitted in the future.
( (
Meta(mspan), Meta(mspan),
tok @ (Open(..) | Close(..) | Ref(..) | RefSubject(..) tok @ (Open(..) | Close(..) | Ref(..) | RefSubject(..)),
| Desc(..)),
) => Transition(Meta(mspan)) ) => Transition(Meta(mspan))
.err(NirToAirError::UnexpectedMetaToken(mspan, tok)), .err(NirToAirError::UnexpectedMetaToken(mspan, tok)),
@ -285,8 +284,8 @@ impl ParseState for NirToAir {
Transition(Ready).ok(Air::RefIdent(spair)) Transition(Ready).ok(Air::RefIdent(spair))
} }
(Ready, Desc(clause)) => { (st @ (Ready | Meta(_)), Desc(clause)) => {
Transition(Ready).ok(Air::DocIndepClause(clause)) Transition(st).ok(Air::DocIndepClause(clause))
} }
(Ready, Import(namespec)) => { (Ready, Import(namespec)) => {

View File

@ -195,6 +195,40 @@ fn tpl_with_name() {
); );
} }
#[test]
fn tpl_with_param() {
let name_tpl = SPair("_tpl_".into(), S2);
let name_param = SPair("@param@".into(), S4);
let desc_param = SPair("param desc".into(), S5);
#[rustfmt::skip]
let toks = vec![
Open(Tpl, S1),
BindIdent(name_tpl),
Open(TplParam, S3),
BindIdent(name_param),
Desc(desc_param),
Close(TplParam, S6),
Close(Tpl, S7),
];
assert_eq!(
#[rustfmt::skip]
Ok(vec![
O(Air::TplStart(S1)),
O(Air::BindIdent(name_tpl)),
O(Air::MetaStart(S3)),
O(Air::BindIdent(name_param)),
O(Air::DocIndepClause(desc_param)),
O(Air::MetaEnd(S6)),
O(Air::TplEnd(S7)),
]),
sut_parse(toks.into_iter()).collect(),
);
}
// Long form takes the actual underscore-padded template name without any // Long form takes the actual underscore-padded template name without any
// additional processing. // additional processing.
#[test] #[test]

View File

@ -1425,9 +1425,10 @@ ele_parse! {
/// expanded. /// expanded.
TplParamStmt := QN_PARAM(_, ospan) { TplParamStmt := QN_PARAM(_, ospan) {
@ { @ {
QN_NAME => TodoAttr, QN_NAME => BindIdent,
QN_DESC => TodoAttr, QN_DESC => Desc,
} => Todo(ospan.into()), } => Nir::Open(NirEntity::TplParam, ospan.into()),
/(cspan) => Nir::Close(NirEntity::TplParam, cspan.into()),
TplParamDefault, TplParamDefault,
}; };

View File

@ -190,5 +190,10 @@
<template name="_match-child_" desc="Template with a match child"> <template name="_match-child_" desc="Template with a match child">
<match on="foo" value="TRUE" /> <match on="foo" value="TRUE" />
</template> </template>
<template name="_tpl-param_" desc="Template with a param">
<param name="@foo@" desc="A parameter" />
<param name="@bar@" desc="Another parameter" />
</template>
</package> </package>

View File

@ -189,5 +189,10 @@
<template name="_match-child_" desc="Template with a match child"> <template name="_match-child_" desc="Template with a match child">
<match on="foo" /> <match on="foo" />
</template> </template>
<template name="_tpl-param_" desc="Template with a param">
<param name="@foo@" desc="A parameter" />
<param name="@bar@" desc="Another parameter" />
</template>
</package> </package>