From d10bf00f5d967028b2c523cdbeefd0acaafb130a Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 14 Jun 2023 16:38:05 -0400 Subject: [PATCH] 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 --- tamer/src/asg/air.rs | 3 +- tamer/src/asg/air/ir.rs | 2 +- tamer/src/asg/air/meta.rs | 21 ++++++++-- tamer/src/asg/air/tpl/test.rs | 28 +++++++++++--- tamer/src/asg/graph/object/meta.rs | 5 ++- tamer/src/asg/graph/xmli.rs | 53 ++++++++++++++++++++++++-- tamer/src/nir/air.rs | 7 ++-- tamer/src/nir/air/test.rs | 34 +++++++++++++++++ tamer/src/nir/parse.rs | 7 ++-- tamer/tests/xmli/template/expected.xml | 5 +++ tamer/tests/xmli/template/src.xml | 5 +++ 11 files changed, 149 insertions(+), 21 deletions(-) diff --git a/tamer/src/asg/air.rs b/tamer/src/asg/air.rs index 5e517585..415904ec 100644 --- a/tamer/src/asg/air.rs +++ b/tamer/src/asg/air.rs @@ -229,7 +229,8 @@ impl ParseState for AirAggregate { } (PkgMeta(meta), AirMeta(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) } diff --git a/tamer/src/asg/air/ir.rs b/tamer/src/asg/air/ir.rs index 6458340c..ca866125 100644 --- a/tamer/src/asg/air/ir.rs +++ b/tamer/src/asg/air/ir.rs @@ -854,7 +854,7 @@ sum_ir! { pub sum enum AirBindableTpl = AirTpl | AirBind | AirDoc; /// Tokens that may be used to define metavariables. - pub sum enum AirBindableMeta = AirMeta | AirBind; + pub sum enum AirBindableMeta = AirMeta | AirBind | AirDoc; } impl AirBind { diff --git a/tamer/src/asg/air/meta.rs b/tamer/src/asg/air/meta.rs index d11adaf1..ab2507b3 100644 --- a/tamer/src/asg/air/meta.rs +++ b/tamer/src/asg/air/meta.rs @@ -64,7 +64,7 @@ impl ParseState for AirMetaAggregate { tok: Self::Token, ctx: &mut Self::Context, ) -> TransitionResult { - use super::ir::{AirBind::*, AirMeta::*}; + use super::ir::{AirBind::*, AirDoc::*, AirMeta::*}; use AirBindableMeta::*; use AirMetaAggregate::*; @@ -91,6 +91,19 @@ impl ParseState for AirMetaAggregate { .map(|_| ()) .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(..))) => { diagnostic_todo!( vec![tok.note("this token")], @@ -119,8 +132,10 @@ impl ParseState for AirMetaAggregate { ) } - // Maybe the bind can be handled by the parent frame. - (Ready, tok @ AirBind(..)) => Transition(Ready).dead(tok), + // Maybe the token can be handled by the parent frame. + (Ready, tok @ (AirBind(..) | AirDoc(..))) => { + Transition(Ready).dead(tok) + } } } diff --git a/tamer/src/asg/air/tpl/test.rs b/tamer/src/asg/air/tpl/test.rs index 8d369ab3..5f457937 100644 --- a/tamer/src/asg/air/tpl/test.rs +++ b/tamer/src/asg/air/tpl/test.rs @@ -462,6 +462,7 @@ fn tpl_with_param() { let id_param1 = SPair("@param1@".into(), S4); let pval1 = SPair("value1".into(), S5); let id_param2 = SPair("@param2@".into(), S8); + let param_desc = SPair("param desc".into(), S9); #[rustfmt::skip] let toks = vec![ @@ -477,8 +478,9 @@ fn tpl_with_param() { // Required metavariable (no value). Air::MetaStart(S7), Air::BindIdent(id_param2), - Air::MetaEnd(S9), - Air::TplEnd(S10), + Air::DocIndepClause(param_desc), + Air::MetaEnd(S10), + Air::TplEnd(S11), ]; let ctx = air_ctx_from_pkg_body_toks(toks); @@ -494,12 +496,28 @@ fn tpl_with_param() { oi_tpl .lookup_local_linear(&asg, *id) .and_then(|oi| oi.edges_filtered::(&asg).next()) - .map(ObjectIndex::cresolve(&asg)) + .map(|oi| (oi, oi.resolve(&asg))) }) .collect::>(); - assert_eq!(params[0], Some(&Meta::Lexeme(S3.merge(S6).unwrap(), pval1))); - assert_eq!(params[1], Some(&Meta::Required(S7.merge(S9).unwrap()))); + assert_eq!( + 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::(&asg) + .last() + .map(ObjectIndex::cresolve(&asg)); + + assert_eq!(doc, Some(&Doc::new_indep_clause(param_desc))); } // A template definition nested within another creates a diff --git a/tamer/src/asg/graph/object/meta.rs b/tamer/src/asg/graph/object/meta.rs index ebc5e9a8..814e9bd4 100644 --- a/tamer/src/asg/graph/object/meta.rs +++ b/tamer/src/asg/graph/object/meta.rs @@ -24,7 +24,7 @@ //! have historically been a feature of the template system. //! The canonical metavariable is the template parameter. -use super::{prelude::*, Ident}; +use super::{prelude::*, Doc, Ident}; use crate::{ diagnose::Annotate, diagnostic_todo, @@ -133,6 +133,9 @@ object_rel! { Meta -> { tree Meta, // TODO: do we need tree? cross Ident, + + // e.g. template paramater description. + tree Doc, } } diff --git a/tamer/src/asg/graph/xmli.rs b/tamer/src/asg/graph/xmli.rs index 87ab17d9..c4b05005 100644 --- a/tamer/src/asg/graph/xmli.rs +++ b/tamer/src/asg/graph/xmli.rs @@ -154,6 +154,10 @@ type TokenStack = ArrayVec; pub struct TreeContext<'a> { stack: TokenStack, asg: &'a Asg, + + /// Whether the most recently encountered template has been interpreted + /// as an application. + tpl_apply: Option>, } impl<'a> TreeContext<'a> { @@ -210,9 +214,10 @@ impl<'a> TreeContext<'a> { self.emit_template(tpl, *oi_tpl, paired_rel.source(), depth) } - Object::Meta((meta, oi_meta)) => { - self.emit_tpl_arg(meta, *oi_meta, depth) - } + Object::Meta((meta, oi_meta)) => match self.tpl_apply { + Some(_) => self.emit_tpl_arg(meta, *oi_meta, depth), + None => self.emit_tpl_param(meta, *oi_meta, depth), + }, Object::Doc((doc, oi_doc)) => { self.emit_doc(doc, *oi_doc, paired_rel.source(), depth) @@ -387,6 +392,7 @@ impl<'a> TreeContext<'a> { ) -> Option { match src { Object::Ident((ident, _)) => { + self.tpl_apply = None; self.push(attr_name(ident.name())); Some(Xirf::open( @@ -404,6 +410,12 @@ impl<'a> TreeContext<'a> { // do not have to deal with converting underscore-padded // template names back into short-hand form. 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, // so we'll handle it ourselves here. // 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, + depth: Depth, + ) -> Option { + 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. /// /// For the parent template application, @@ -501,6 +540,13 @@ impl<'a> TreeContext<'a> { 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)) => { // TODO: This isn't utilized by the XSLT parser and // `xmllint` for system tests does not format with mixed @@ -564,6 +610,7 @@ impl<'a> From<&'a Asg> for TreeContext<'a> { TreeContext { stack: Default::default(), asg, + tpl_apply: None, } } } diff --git a/tamer/src/nir/air.rs b/tamer/src/nir/air.rs index 4b7a710f..2512aa96 100644 --- a/tamer/src/nir/air.rs +++ b/tamer/src/nir/air.rs @@ -263,8 +263,7 @@ impl ParseState for NirToAir { // Some of these will be permitted in the future. ( Meta(mspan), - tok @ (Open(..) | Close(..) | Ref(..) | RefSubject(..) - | Desc(..)), + tok @ (Open(..) | Close(..) | Ref(..) | RefSubject(..)), ) => Transition(Meta(mspan)) .err(NirToAirError::UnexpectedMetaToken(mspan, tok)), @@ -285,8 +284,8 @@ impl ParseState for NirToAir { Transition(Ready).ok(Air::RefIdent(spair)) } - (Ready, Desc(clause)) => { - Transition(Ready).ok(Air::DocIndepClause(clause)) + (st @ (Ready | Meta(_)), Desc(clause)) => { + Transition(st).ok(Air::DocIndepClause(clause)) } (Ready, Import(namespec)) => { diff --git a/tamer/src/nir/air/test.rs b/tamer/src/nir/air/test.rs index 03abc7f4..1807024f 100644 --- a/tamer/src/nir/air/test.rs +++ b/tamer/src/nir/air/test.rs @@ -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 // additional processing. #[test] diff --git a/tamer/src/nir/parse.rs b/tamer/src/nir/parse.rs index 3358c60a..8f477268 100644 --- a/tamer/src/nir/parse.rs +++ b/tamer/src/nir/parse.rs @@ -1425,9 +1425,10 @@ ele_parse! { /// expanded. TplParamStmt := QN_PARAM(_, ospan) { @ { - QN_NAME => TodoAttr, - QN_DESC => TodoAttr, - } => Todo(ospan.into()), + QN_NAME => BindIdent, + QN_DESC => Desc, + } => Nir::Open(NirEntity::TplParam, ospan.into()), + /(cspan) => Nir::Close(NirEntity::TplParam, cspan.into()), TplParamDefault, }; diff --git a/tamer/tests/xmli/template/expected.xml b/tamer/tests/xmli/template/expected.xml index eee388f0..956e518b 100644 --- a/tamer/tests/xmli/template/expected.xml +++ b/tamer/tests/xmli/template/expected.xml @@ -190,5 +190,10 @@ + + diff --git a/tamer/tests/xmli/template/src.xml b/tamer/tests/xmli/template/src.xml index 73394df1..5ff5c35f 100644 --- a/tamer/tests/xmli/template/src.xml +++ b/tamer/tests/xmli/template/src.xml @@ -189,5 +189,10 @@ + +