From b30018c23b340d3617d6a1f7bd0a79e230fc8f62 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Thu, 13 Jul 2023 16:41:27 -0400 Subject: [PATCH] tamer: xmli reconstruction of desugared interpolated metavars Well, this is both good news and bad news. The good news is that this finally produces the expected output and reconstructs sources from interpolated values on the ASG. Yay! ...the bad news is that it's wrong. Notice how the fixpoint test is disabled. So, my plan was originally to commit it like this first and see if I was comfortable relaxing the convention that `` nodes had to appear in the header. That's nice to do, that's cleaner to do, but would the XSLT-based compiler really care? I had to investigate. Well, turns out that TAMER does care. Because, well over a decade ago, I re-used ``, which could represent not only a template param, but also a global param, or a function param. So, XML->NIR considers all `` nodes at the head of a template to be template parameters. But after the first non-header element, we transition to another state that allows it to be pretty much anything. And so, I can't relax that restriction. And because of that, I can't just stream the tree to the xmli generator, I'll have to queue up nodes and order them. Oh well, I tried. DEV-13163 --- tamer/src/asg/graph/object/meta.rs | 14 +++ tamer/src/asg/graph/xmli.rs | 106 ++++++++++++++----- tamer/src/nir/air.rs | 6 +- tamer/src/nir/air/test.rs | 2 + tamer/tests/xmli/meta-interp/expected.xml | 79 ++++++++++++++ tamer/tests/xmli/meta-interp/is-experimental | 0 tamer/tests/xmli/meta-interp/no-fixpoint | 0 tamer/tests/xmli/meta-interp/src.xml | 80 ++++++++++++++ tamer/tests/xmli/test-xmli | 7 +- 9 files changed, 263 insertions(+), 31 deletions(-) create mode 100644 tamer/tests/xmli/meta-interp/expected.xml create mode 100644 tamer/tests/xmli/meta-interp/is-experimental create mode 100644 tamer/tests/xmli/meta-interp/no-fixpoint create mode 100644 tamer/tests/xmli/meta-interp/src.xml diff --git a/tamer/src/asg/graph/object/meta.rs b/tamer/src/asg/graph/object/meta.rs index 39bf4159..afe17f65 100644 --- a/tamer/src/asg/graph/object/meta.rs +++ b/tamer/src/asg/graph/object/meta.rs @@ -118,6 +118,20 @@ impl Meta { ), } } + + /// Retrieve a concrete lexeme, + /// if any. + /// + /// If this metavariable represents a concatenation list, + /// this will return [`None`]. + /// This method _does not_ expand metavariables, + /// and does not have the context necessary to do so. + pub fn lexeme(&self) -> Option { + match self { + Self::Required(_) | Self::ConcatList(_) => None, + Self::Lexeme(_, lex) => Some(*lex), + } + } } impl From<&Meta> for Span { diff --git a/tamer/src/asg/graph/xmli.rs b/tamer/src/asg/graph/xmli.rs index 9cc3d105..1a95013e 100644 --- a/tamer/src/asg/graph/xmli.rs +++ b/tamer/src/asg/graph/xmli.rs @@ -193,18 +193,30 @@ impl<'a> TreeContext<'a> { ), }, - // Identifiers will be considered in context; - // pass over it for now. - // But we must not skip over its depth, - // otherwise we parent a following sibling at a matching - // depth; - // this close will force the auto-closing system to close - // any siblings in preparation for the object to follow. - Object::Ident((ident, _)) => Some(Xirf::Close( - None, - CloseSpan::without_name_span(ident.span()), - depth, - )), + Object::Ident((ident, oi_ident)) => match paired_rel.source() { + Object::Meta(..) => { + self.emit_tpl_param_value(ident, oi_ident, depth) + } + + // All other identifiers will be considered in context; + // pass over it for now. + // But we must not skip over its depth, + // otherwise we parent a following sibling at a matching + // depth; + // this close will force the auto-closing system to + // close any siblings in preparation for the object to + // follow. + Object::Root(..) + | Object::Pkg(..) + | Object::Ident(..) + | Object::Expr(..) + | Object::Tpl(..) + | Object::Doc(..) => Some(Xirf::Close( + None, + CloseSpan::without_name_span(ident.span()), + depth, + )), + }, Object::Expr((expr, oi_expr)) => { self.emit_expr(expr, *oi_expr, paired_rel.source(), depth) @@ -282,8 +294,9 @@ impl<'a> TreeContext<'a> { depth: Depth, ) -> Option { match src { - Object::Ident((ident, oi_ident)) => { - self.emit_expr_ident(expr, *oi_ident, ident, depth) + Object::Ident((_, oi_ident)) => { + let name = oi_ident.name_or_meta(self.asg); + self.emit_expr_ident(expr, name, depth) } Object::Expr((pexpr, _)) => match (pexpr.op(), expr.op()) { (ExprOp::Conj | ExprOp::Disj, ExprOp::Eq) => { @@ -315,8 +328,7 @@ impl<'a> TreeContext<'a> { fn emit_expr_ident( &mut self, expr: &Expr, - oi_ident: ObjectIndex, - ident: &Ident, + name: SPair, depth: Depth, ) -> Option { let (qname, ident_qname) = match expr.op() { @@ -332,9 +344,8 @@ impl<'a> TreeContext<'a> { } }; - let name = oi_ident.name_or_meta(self.asg); - let ispan = ident.span(); - self.push(Xirf::attr(ident_qname, name, (ispan, ispan))); + let span = name.span(); + self.push(Xirf::attr(ident_qname, name, (span, span))); Some(Xirf::open( qname, @@ -462,18 +473,57 @@ impl<'a> TreeContext<'a> { oi_meta: ObjectIndex, depth: Depth, ) -> Option { - let pname = oi_meta - .ident(self.asg) - .map(|oi| oi.name_or_meta(self.asg)) - .diagnostic_unwrap(|| { - vec![meta.internal_error("missing param name")] - }); + if let Some(pname) = + oi_meta.ident(self.asg).map(|oi| oi.name_or_meta(self.asg)) + { + self.push(attr_name(pname)); - self.push(attr_name(pname)); + Some(Xirf::open( + QN_PARAM, + OpenSpan::without_name_span(meta.span()), + depth, + )) + } else if let Some(lexeme) = meta.lexeme() { + self.push(Xirf::close( + Some(QN_TEXT), + CloseSpan::without_name_span(meta.span()), + depth, + )); + + self.push(Xirf::text( + Text(lexeme.symbol(), lexeme.span()), + depth.child_depth(), + )); + + Some(Xirf::open( + QN_TEXT, + OpenSpan::without_name_span(meta.span()), + depth, + )) + } else { + // TODO: Rewrite the above to be an exhaustive match, perhaps, + // so we know what we'll error on. + diagnostic_todo!( + vec![oi_meta.internal_error("unsupported Meta type")], + "xmli output does not yet support this Meta object", + ) + } + } + + /// Emit a `` node assumed to be within a template param + /// body. + fn emit_tpl_param_value( + &mut self, + ident: &Ident, + oi_ident: &ObjectIndex, + depth: Depth, + ) -> Option { + let name = oi_ident.name_or_meta(self.asg); + self.push(attr_name(name)); Some(Xirf::open( - QN_PARAM, - OpenSpan::without_name_span(meta.span()), + QN_PARAM_VALUE, + OpenSpan::without_name_span(ident.span()), depth, )) } diff --git a/tamer/src/nir/air.rs b/tamer/src/nir/air.rs index 082e97f9..0376544c 100644 --- a/tamer/src/nir/air.rs +++ b/tamer/src/nir/air.rs @@ -255,6 +255,9 @@ impl ParseState for NirToAir { (Meta(mspan), BindIdentMeta(spair)) => { Transition(Meta(mspan)).ok(Air::BindIdent(spair)) } + (Meta(mspan), Ref(spair)) => { + Transition(Meta(mspan)).ok(Air::RefIdent(spair)) + } (Meta(mspan), Text(lexeme)) => { Transition(Meta(mspan)).ok(Air::MetaLexeme(lexeme)) } @@ -264,8 +267,7 @@ impl ParseState for NirToAir { // Some of these will be permitted in the future. ( Meta(mspan), - tok @ (Open(..) | Close(..) | BindIdent(..) | Ref(..) - | RefSubject(..)), + tok @ (Open(..) | Close(..) | BindIdent(..) | RefSubject(..)), ) => Transition(Meta(mspan)) .err(NirToAirError::ExpectedMetaToken(mspan, tok)), diff --git a/tamer/src/nir/air/test.rs b/tamer/src/nir/air/test.rs index 603665af..86ecba11 100644 --- a/tamer/src/nir/air/test.rs +++ b/tamer/src/nir/air/test.rs @@ -269,6 +269,7 @@ fn apply_template_long_form_args() { Open(TplParam, S3), BindIdentMeta(p1), Text(v1), + Ref(p2), Close(TplParam, S6), Open(TplParam, S7), @@ -287,6 +288,7 @@ fn apply_template_long_form_args() { O(Air::MetaStart(S3)), O(Air::BindIdent(p1)), O(Air::MetaLexeme(v1)), + O(Air::RefIdent(p2)), O(Air::MetaEnd(S6)), O(Air::MetaStart(S7)), diff --git a/tamer/tests/xmli/meta-interp/expected.xml b/tamer/tests/xmli/meta-interp/expected.xml new file mode 100644 index 00000000..df8c5699 --- /dev/null +++ b/tamer/tests/xmli/meta-interp/expected.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + diff --git a/tamer/tests/xmli/meta-interp/is-experimental b/tamer/tests/xmli/meta-interp/is-experimental new file mode 100644 index 00000000..e69de29b diff --git a/tamer/tests/xmli/meta-interp/no-fixpoint b/tamer/tests/xmli/meta-interp/no-fixpoint new file mode 100644 index 00000000..e69de29b diff --git a/tamer/tests/xmli/meta-interp/src.xml b/tamer/tests/xmli/meta-interp/src.xml new file mode 100644 index 00000000..28936499 --- /dev/null +++ b/tamer/tests/xmli/meta-interp/src.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + diff --git a/tamer/tests/xmli/test-xmli b/tamer/tests/xmli/test-xmli index 5149d8cf..5ef36fbb 100755 --- a/tamer/tests/xmli/test-xmli +++ b/tamer/tests/xmli/test-xmli @@ -69,7 +69,7 @@ timed-tamec() { header() { # allocate enough space based on the path we'll output local -i mypath_len=${#mypath} - local -i dirlen=$((mypath_len + 12)) + local -i dirlen=$((mypath_len + 14)) # newline intentionally omitted printf "%-${dirlen}s %-20s " "$@" @@ -104,6 +104,11 @@ test-derive-from-src() { test-fixpoint() { local dir="${1?Missing directory name}" + if [ -f "$dir/no-fixpoint" ]; then + echo -n '!!!WARNING!!! test skipped: `no-fixpoint` file ' + return + fi + timed-tamec "$dir" out.xmli out-2.xmli || return diff <("$P_XMLLINT" --format "$dir/expected.xml" || echo 'ERR expected.xml') \