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,
|
/// then the param is shorthand,
|
||||||
/// with the non-`@`-padded name as the first of the pair and the
|
/// with the non-`@`-padded name as the first of the pair and the
|
||||||
/// value as the second.
|
/// value as the second.
|
||||||
|
///
|
||||||
|
/// A shorthand entity is implicitly closed and should not have a
|
||||||
|
/// matching [`Nir::Close`] token.
|
||||||
TplParam(Option<(SPair, SPair)>),
|
TplParam(Option<(SPair, SPair)>),
|
||||||
|
|
||||||
/// Full application and expansion a template.
|
/// Full application and expansion a template.
|
||||||
|
|
|
@ -124,7 +124,13 @@ impl ParseState for NirToAir {
|
||||||
// Short-hand template application must be handled through
|
// Short-hand template application must be handled through
|
||||||
// desugaring as part of the lowering pipeline,
|
// desugaring as part of the lowering pipeline,
|
||||||
// so that it is converted to long form before getting here.
|
// 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
|
// TODO: In the future maybe TAMER will have evolved its
|
||||||
// abstractions enough that there's an ROI for prohibiting
|
// abstractions enough that there's an ROI for prohibiting
|
||||||
// this at the type level.
|
// this at the type level.
|
||||||
|
@ -147,6 +153,16 @@ impl ParseState for NirToAir {
|
||||||
Transition(Ready).ok(Air::TplEndRef(span))
|
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,
|
Ready,
|
||||||
Nir::Close(
|
Nir::Close(
|
||||||
|
@ -169,15 +185,9 @@ impl ParseState for NirToAir {
|
||||||
Transition(Ready).ok(Air::RefIdent(spair))
|
Transition(Ready).ok(Air::RefIdent(spair))
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(Ready, Nir::Todo | Nir::TodoAttr(..) | Nir::Desc(..)) => {
|
||||||
Ready,
|
Transition(Ready).ok(Air::Todo(UNKNOWN_SPAN))
|
||||||
Nir::Todo
|
}
|
||||||
| Nir::TodoAttr(..)
|
|
||||||
| Nir::Desc(..)
|
|
||||||
| Nir::Text(_)
|
|
||||||
| Nir::Open(NirEntity::TplParam(_), _)
|
|
||||||
| Nir::Close(NirEntity::TplParam(_), _),
|
|
||||||
) => 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
|
// Long form takes the actual underscore-padded template name without any
|
||||||
// additional processing.
|
// additional processing.
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_template_long_form() {
|
fn apply_template_long_form_nullary() {
|
||||||
let name = SPair("_tpl-name_".into(), S2);
|
let name = SPair("_tpl-name_".into(), S2);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -161,3 +161,49 @@ fn apply_template_long_form() {
|
||||||
Sut::parse(toks.into_iter()).collect(),
|
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@`
|
/// and where the body of this application is the `@values@`
|
||||||
/// template argument.
|
/// template argument.
|
||||||
/// See [`ApplyTemplate`] for more information.
|
/// 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) {
|
TplApplyShort := NS_T(qname, ospan) {
|
||||||
@ {} => Nir::Open(NirEntity::TplApply(Some(qname)), ospan.into()),
|
@ {} => Nir::Open(NirEntity::TplApply(Some(qname)), ospan.into()),
|
||||||
/(cspan) => Nir::Close(NirEntity::TplApply(None), cspan.into()),
|
/(cspan) => Nir::Close(NirEntity::TplApply(None), cspan.into()),
|
||||||
|
@ -1702,7 +1709,18 @@ ele_parse! {
|
||||||
// Streaming attribute parsing;
|
// Streaming attribute parsing;
|
||||||
// this takes precedence over any attribute parsing above
|
// this takes precedence over any attribute parsing above
|
||||||
// (which is used only for emitting the opening object).
|
// (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,
|
// Template bodies depend on context,
|
||||||
// so we have to just accept everything and defer to a future
|
// 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))
|
.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.
|
// 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)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
|
@ -35,20 +35,103 @@ fn desugars_nullary() {
|
||||||
Nir::Close(NirEntity::TplApply(None), S2),
|
Nir::Close(NirEntity::TplApply(None), S2),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
O(Nir::Open(NirEntity::TplApply(None), S1)),
|
O(Nir::Open(NirEntity::TplApply(None), S1)),
|
||||||
// The span associated with the name of the template to be
|
// The span associated with the name of the template to be
|
||||||
// applied is the span of the entire QName from NIR.
|
// applied is the span of the entire QName from NIR.
|
||||||
// The reason for this is that `t:foo` is transformed into
|
// The reason for this is that `t:foo` is transformed into
|
||||||
// `_foo_`,
|
// `_foo_`,
|
||||||
// and so the `t:` is a necessary part of the
|
// and so the `t:` is a necessary part of the
|
||||||
// representation of the name of the template;
|
// representation of the name of the template;
|
||||||
// `foo` is not in itself a valid template name at the
|
// `foo` is not in itself a valid template name at the
|
||||||
// time of writing.
|
// time of writing.
|
||||||
O(Nir::Ref(SPair(tpl, S1))),
|
O(Nir::Ref(SPair(tpl, S1))),
|
||||||
O(Nir::Close(NirEntity::TplApply(None), S2)),
|
O(Nir::Close(NirEntity::TplApply(None), S2)),
|
||||||
]),
|
]),
|
||||||
Sut::parse(toks.into_iter()).collect(),
|
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 forms (`[sp](args) => expr`).
|
||||||
$(
|
$(
|
||||||
[$special:ident]$(($($special_arg:ident),*))?
|
[$special:ident]$(($($special_arg:tt)*))?
|
||||||
=> $special_map:expr,
|
=> $special_map:expr,
|
||||||
)?
|
)?
|
||||||
|
|
||||||
|
@ -338,7 +338,7 @@ macro_rules! ele_parse {
|
||||||
@ { $($attrbody)* } => $attrmap,
|
@ { $($attrbody)* } => $attrmap,
|
||||||
/$($($close_span)?)? => ele_parse!(@!ele_close $($closemap)?),
|
/$($($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,
|
/$($close_span:ident)? => $closemap:expr,
|
||||||
|
|
||||||
// Streaming (as opposed to aggregate) attribute parsing.
|
// 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.
|
// Nonterminal references.
|
||||||
<> {
|
<> {
|
||||||
|
|
Loading…
Reference in New Issue