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
parent
d3f8f76904
commit
91b787d367
|
@ -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.
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Nir, 2>;
|
||||
type Stack = ArrayVec<Nir, 3>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
|
|
@ -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![
|
||||
// <t:qname
|
||||
Nir::Open(NirEntity::TplApply(Some(qname)), S1),
|
||||
// foo="foo value"
|
||||
Nir::Open(NirEntity::TplParam(Some((aname, pval))), S2),
|
||||
// Implicit close.
|
||||
// />
|
||||
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(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
<> {
|
||||
|
|
Loading…
Reference in New Issue