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 `<param>` 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 `<param>`, which could represent not only a template param, but also
a global param, or a function param.

So, XML->NIR considers all `<param>` 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
main
Mike Gerwitz 2023-07-13 16:41:27 -04:00
parent 85892caeb2
commit b30018c23b
9 changed files with 263 additions and 31 deletions

View File

@ -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<SPair> {
match self {
Self::Required(_) | Self::ConcatList(_) => None,
Self::Lexeme(_, lex) => Some(*lex),
}
}
}
impl From<&Meta> for Span {

View File

@ -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<Xirf> {
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: &Ident,
name: SPair,
depth: Depth,
) -> Option<Xirf> {
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<Meta>,
depth: Depth,
) -> Option<Xirf> {
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 `<param-value>` node assumed to be within a template param
/// body.
fn emit_tpl_param_value(
&mut self,
ident: &Ident,
oi_ident: &ObjectIndex<Ident>,
depth: Depth,
) -> Option<Xirf> {
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,
))
}

View File

@ -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)),

View File

@ -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)),

View File

@ -0,0 +1,79 @@
<package xmlns="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:t="http://www.lovullo.com/rater/apply-template">
<template name="_interp-non-bind_"
desc="Interpolation in non-binding position">
<classify as="only" desc="@___dsgr_335@"/>
<param name="@___dsgr_335@"
desc="Generated from interpolated string `{@bar@}`">
<param-value name="@bar@" />
</param>
<classify as="prefixed" desc="@___dsgr_368@"/>
<param name="@___dsgr_368@"
desc="Generated from interpolated string `Prefix {@bar@}`">
<text>Prefix </text>
<param-value name="@bar@" />
</param>
<classify as="suffixed" desc="@___dsgr_3a3@"/>
<param name="@___dsgr_3a3@"
desc="Generated from interpolated string `{@bar@} suffix`">
<param-value name="@bar@" />
<text> suffix</text>
</param>
<classify as="both" desc="@___dsgr_3da@" />
<param name="@___dsgr_3da@"
desc="Generated from interpolated string `Prefix {@bar@} suffix`">
<text>Prefix </text>
<param-value name="@bar@" />
<text> suffix</text>
</param>
</template>
<template name="_with-abstract-ident_"
desc="Metavariable interpolation in binding position">
<param name="@___dsgr_4a8@"
desc="Generated from interpolated string `{@as@}`">
<param-value name="@as@" />
</param>
<classify as="@___dsgr_4a8@" />
<param name="@___dsgr_4ca@"
desc="Generated from interpolated string `prefix-{@as@}`">
<text>prefix-</text>
<param-value name="@as@" />
</param>
<classify as="@___dsgr_4ca@" />
<param name="@___dsgr_4f4@"
desc="Generated from interpolated string `{@as@}-suffix`">
<param-value name="@as@" />
<text>-suffix</text>
</param>
<classify as="@___dsgr_4f4@" />
<param name="@___dsgr_51e@"
desc="Generated from interpolated string `prefix-{@as@}-suffix`">
<text>prefix-</text>
<param-value name="@as@" />
<text>-suffix</text>
</param>
<classify as="@___dsgr_51e@" />
</template>
</package>

View File

@ -0,0 +1,80 @@
<?xml version="1.0"?>
<package xmlns="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:t="http://www.lovullo.com/rater/apply-template">
<!-- note: the extra vertical space is for alignment with expected.xml;
open them side-by-side in your editor of choice -->
<!-- because the output contains identifiers derived from spans, this test
is exceptionally fragile; if you add or remove a single byte, you're
bound to break things. If that happens, it is safe to update the
span portion of identifier names. In the future, a tool may be
created to help with this tedious chore. -->
<template name="_interp-non-bind_"
desc="Interpolation in non-binding position">
<!-- note the `{}` here -->
<classify as="only" desc="{@bar@}" />
<classify as="prefixed" desc="Prefix {@bar@}" />
<classify as="suffixed" desc="{@bar@} suffix" />
<classify as="both" desc="Prefix {@bar@} suffix" />
</template>
<template name="_with-abstract-ident_"
desc="Metavariable interpolation in binding position">
<!-- note the `{}` here -->
<classify as="{@as@}" />
<classify as="prefix-{@as@}" />
<classify as="{@as@}-suffix" />
<classify as="prefix-{@as@}-suffix" />
</template>
</package>

View File

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