From 91b787d367f3708d652e0bb184a228b56bef3ff4 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 22 Mar 2023 10:07:16 -0400 Subject: [PATCH] tamer: nir: Desugar shorthand template params and yield AIR I had intended for this to be a full vertical slice initially, but AIR's parser is going to need enough work that it'll muddy this patch a bit too much. This keeps the desugaring simple, which is what I was hoping for. The next step is to load it into the graph and emit regenerated longhand sources. I also don't like how the namespace prefix is just being ignored for shorthand param desugaring. This is also the case in the XSLT-based compiler, but this violates TAMER's principle that it should parse every bit of information; nothing should be ignored. If something does not contribute useful information, then it is not a useful construct and ought to be rejected. DEV-13708 --- tamer/src/nir.rs | 3 + tamer/src/nir/air.rs | 30 ++++++---- tamer/src/nir/air/test.rs | 48 +++++++++++++++- tamer/src/nir/parse.rs | 20 ++++++- tamer/src/nir/tplshort.rs | 20 ++++++- tamer/src/nir/tplshort/test.rs | 101 ++++++++++++++++++++++++++++++--- tamer/src/xir/parse/ele.rs | 6 +- 7 files changed, 202 insertions(+), 26 deletions(-) diff --git a/tamer/src/nir.rs b/tamer/src/nir.rs index 8e6cdc0d..3f1ab9d2 100644 --- a/tamer/src/nir.rs +++ b/tamer/src/nir.rs @@ -210,6 +210,9 @@ pub enum NirEntity { /// then the param is shorthand, /// with the non-`@`-padded name as the first of the pair and the /// value as the second. + /// + /// A shorthand entity is implicitly closed and should not have a + /// matching [`Nir::Close`] token. TplParam(Option<(SPair, SPair)>), /// Full application and expansion a template. diff --git a/tamer/src/nir/air.rs b/tamer/src/nir/air.rs index 49b570fd..488cd573 100644 --- a/tamer/src/nir/air.rs +++ b/tamer/src/nir/air.rs @@ -124,7 +124,13 @@ impl ParseState for NirToAir { // Short-hand template application must be handled through // desugaring as part of the lowering pipeline, // so that it is converted to long form before getting here. - (Ready, Nir::Open(NirEntity::TplApply(Some(_)), span)) => { + ( + Ready, + Nir::Open( + NirEntity::TplApply(Some(_)) | NirEntity::TplParam(Some(_)), + span, + ), + ) => { // TODO: In the future maybe TAMER will have evolved its // abstractions enough that there's an ROI for prohibiting // this at the type level. @@ -147,6 +153,16 @@ impl ParseState for NirToAir { Transition(Ready).ok(Air::TplEndRef(span)) } + (Ready, Nir::Open(NirEntity::TplParam(None), span)) => { + Transition(Ready).ok(Air::TplMetaStart(span)) + } + (Ready, Nir::Close(NirEntity::TplParam(_), span)) => { + Transition(Ready).ok(Air::TplMetaEnd(span)) + } + (Ready, Nir::Text(lexeme)) => { + Transition(Ready).ok(Air::TplLexeme(lexeme)) + } + ( Ready, Nir::Close( @@ -169,15 +185,9 @@ impl ParseState for NirToAir { Transition(Ready).ok(Air::RefIdent(spair)) } - ( - Ready, - Nir::Todo - | Nir::TodoAttr(..) - | Nir::Desc(..) - | Nir::Text(_) - | Nir::Open(NirEntity::TplParam(_), _) - | Nir::Close(NirEntity::TplParam(_), _), - ) => Transition(Ready).ok(Air::Todo(UNKNOWN_SPAN)), + (Ready, Nir::Todo | Nir::TodoAttr(..) | Nir::Desc(..)) => { + Transition(Ready).ok(Air::Todo(UNKNOWN_SPAN)) + } } } diff --git a/tamer/src/nir/air/test.rs b/tamer/src/nir/air/test.rs index cab93580..4bc09572 100644 --- a/tamer/src/nir/air/test.rs +++ b/tamer/src/nir/air/test.rs @@ -141,7 +141,7 @@ fn tpl_with_name() { // Long form takes the actual underscore-padded template name without any // additional processing. #[test] -fn apply_template_long_form() { +fn apply_template_long_form_nullary() { let name = SPair("_tpl-name_".into(), S2); #[rustfmt::skip] @@ -161,3 +161,49 @@ fn apply_template_long_form() { Sut::parse(toks.into_iter()).collect(), ); } + +#[test] +fn apply_template_long_form_args() { + let name = SPair("_tpl-name_".into(), S2); + let p1 = SPair("@p1@".into(), S4); + let v1 = SPair("value1".into(), S5); + let p2 = SPair("@p2@".into(), S8); + let v2 = SPair("value2".into(), S9); + + #[rustfmt::skip] + let toks = vec![ + Nir::Open(NirEntity::TplApply(None), S1), + Nir::Ref(name), + + Nir::Open(NirEntity::TplParam(None), S3), + Nir::BindIdent(p1), + Nir::Text(v1), + Nir::Close(NirEntity::TplParam(None), S6), + + Nir::Open(NirEntity::TplParam(None), S7), + Nir::BindIdent(p2), + Nir::Text(v2), + Nir::Close(NirEntity::TplParam(None), S10), + Nir::Close(NirEntity::TplApply(None), S11), + ]; + + #[rustfmt::skip] + assert_eq!( + Ok(vec![ + O(Air::TplStart(S1)), + O(Air::RefIdent(name)), + + O(Air::TplMetaStart(S3)), + O(Air::BindIdent(p1)), + O(Air::TplLexeme(v1)), + O(Air::TplMetaEnd(S6)), + + O(Air::TplMetaStart(S7)), + O(Air::BindIdent(p2)), + O(Air::TplLexeme(v2)), + O(Air::TplMetaEnd(S10)), + O(Air::TplEndRef(S11)), + ]), + Sut::parse(toks.into_iter()).collect(), + ); +} diff --git a/tamer/src/nir/parse.rs b/tamer/src/nir/parse.rs index d97d8dff..01c0dd0a 100644 --- a/tamer/src/nir/parse.rs +++ b/tamer/src/nir/parse.rs @@ -1695,6 +1695,13 @@ ele_parse! { /// and where the body of this application is the `@values@` /// template argument. /// See [`ApplyTemplate`] for more information. + /// + /// The name of the template omits the surrounding `_`s; + /// `t:foo` will desugar into the template name `_foo_`. + /// Params similarly omit `@` and are derived from the _local name + /// only_; + /// so `bar="baz"` will be desugared into a param `@bar@` with a + /// text value `baz`. TplApplyShort := NS_T(qname, ospan) { @ {} => Nir::Open(NirEntity::TplApply(Some(qname)), ospan.into()), /(cspan) => Nir::Close(NirEntity::TplApply(None), cspan.into()), @@ -1702,7 +1709,18 @@ ele_parse! { // Streaming attribute parsing; // this takes precedence over any attribute parsing above // (which is used only for emitting the opening object). - [attr](_attr) => Todo, + [attr](Attr(name, value, AttrSpan(name_span, value_span))) => { + Nir::Open( + // TODO: This simply _ignores_ the namespace prefix. + // If it's not a useful construct, + // it ought to be rejected. + NirEntity::TplParam(Some(( + SPair(*name.local_name(), name_span), + SPair(value, value_span), + ))), + name_span, + ) + }, // Template bodies depend on context, // so we have to just accept everything and defer to a future diff --git a/tamer/src/nir/tplshort.rs b/tamer/src/nir/tplshort.rs index 86f05f8c..a6d09c30 100644 --- a/tamer/src/nir/tplshort.rs +++ b/tamer/src/nir/tplshort.rs @@ -124,8 +124,24 @@ impl ParseState for TplShortDesugar { .ok(Nir::Open(NirEntity::TplApply(None), span)) } + // Shorthand template params' names do not contain the + // surrounding `@`s. + ( + Scanning, + Nir::Open(NirEntity::TplParam(Some((name, val))), span), + ) => { + let pname = format!("@{name}@").intern(); + + stack.push(Nir::Close(NirEntity::TplParam(None), span)); + stack.push(Nir::Text(val)); + stack.push(Nir::BindIdent(SPair(pname, name.span()))); + + Transition(Scanning) + .ok(Nir::Open(NirEntity::TplParam(None), span)) + } + // Any tokens that we don't recognize will be passed on unchanged. - (Scanning, tok) => Transition(Scanning).ok(tok), + (st, tok) => Transition(st).ok(tok), } } @@ -134,7 +150,7 @@ impl ParseState for TplShortDesugar { } } -type Stack = ArrayVec; +type Stack = ArrayVec; #[cfg(test)] mod test; diff --git a/tamer/src/nir/tplshort/test.rs b/tamer/src/nir/tplshort/test.rs index 026e46b6..02e26ff1 100644 --- a/tamer/src/nir/tplshort/test.rs +++ b/tamer/src/nir/tplshort/test.rs @@ -35,20 +35,103 @@ fn desugars_nullary() { Nir::Close(NirEntity::TplApply(None), S2), ]; + #[rustfmt::skip] assert_eq!( Ok(vec![ O(Nir::Open(NirEntity::TplApply(None), 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(Nir::Ref(SPair(tpl, 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(Nir::Ref(SPair(tpl, S1))), O(Nir::Close(NirEntity::TplApply(None), S2)), ]), Sut::parse(toks.into_iter()).collect(), ); } + +#[test] +fn desugars_unary() { + // Shorthand converts `t:tpl-name` into `_tpl-name_`. + let qname = ("t", "tpl-name").unwrap_into(); + let name = SPair("_tpl-name_".into(), S1); + + let aname = SPair("foo".into(), S3); + let pval = SPair("foo value".into(), S4); + + // The attribute name gets padded with '@', + // much like the template does with underscores. + let pname = SPair("@foo@".into(), S3); + + #[rustfmt::skip] + let toks = vec![ + // + Nir::Close(NirEntity::TplApply(None), S6), + ]; + + #[rustfmt::skip] + assert_eq!( + Ok(vec![ + O(Nir::Open(NirEntity::TplApply(None), S1)), + O(Nir::Ref(name)), + + O(Nir::Open(NirEntity::TplParam(None), S2)), + // Derived from `aname` (by padding) + O(Nir::BindIdent(pname)), + // The value is left untouched. + O(Nir::Text(pval)), + // Close is derived from open. + O(Nir::Close(NirEntity::TplParam(None), S2)), + O(Nir::Close(NirEntity::TplApply(None), S6)), + ]), + Sut::parse(toks.into_iter()).collect(), + ); +} + +// Don't parse what we desugar into! +#[test] +fn does_not_desugar_long_form() { + let name = SPair("_tpl-name_".into(), S2); + let pname = SPair("@param".into(), S4); + let pval = SPair("value".into(), S5); + + #[rustfmt::skip] + let toks = [ + Nir::Open(NirEntity::TplApply(None), S1), + Nir::BindIdent(name), + + Nir::Open(NirEntity::TplParam(None), S3), + Nir::BindIdent(pname), + Nir::Text(pval), + Nir::Close(NirEntity::TplParam(None), S6), + Nir::Close(NirEntity::TplApply(None), S7), + ]; + + #[rustfmt::skip] + assert_eq!( + // We avoid #[derive(Clone)] on Nir so that we have confidence that + // we're not doing anything suspicious with tokens. + // So this is a duplicate of the above, + // mapped over `O`. + Ok(vec![ + O(Nir::Open(NirEntity::TplApply(None), S1)), + O(Nir::BindIdent(name)), + + O(Nir::Open(NirEntity::TplParam(None), S3)), + O(Nir::BindIdent(pname)), + O(Nir::Text(pval)), + O(Nir::Close(NirEntity::TplParam(None), S6)), + O(Nir::Close(NirEntity::TplApply(None), S7)), + ]), + Sut::parse(toks.into_iter()).collect(), + ); +} diff --git a/tamer/src/xir/parse/ele.rs b/tamer/src/xir/parse/ele.rs index 9a39bf32..1840e90b 100644 --- a/tamer/src/xir/parse/ele.rs +++ b/tamer/src/xir/parse/ele.rs @@ -320,7 +320,7 @@ macro_rules! ele_parse { // Special forms (`[sp](args) => expr`). $( - [$special:ident]$(($($special_arg:ident),*))? + [$special:ident]$(($($special_arg:tt)*))? => $special_map:expr, )? @@ -338,7 +338,7 @@ macro_rules! ele_parse { @ { $($attrbody)* } => $attrmap, /$($($close_span)?)? => ele_parse!(@!ele_close $($closemap)?), - $([$special]$(($($special_arg),*))? => $special_map,)? + $([$special]$(($($special_arg)*))? => $special_map,)? <> { $( @@ -422,7 +422,7 @@ macro_rules! ele_parse { /$($close_span:ident)? => $closemap:expr, // Streaming (as opposed to aggregate) attribute parsing. - $([attr]($attr_stream_binding:ident) => $attr_stream_map:expr,)? + $([attr]($attr_stream_binding:pat) => $attr_stream_map:expr,)? // Nonterminal references. <> {