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') \