tamer: asg::air: Template support for dangling expressions
The intent was to have a very simple implementation of `hold_dangling` and have everything work. But, I had a nasty surprise when the system tests caught bug caused by some interesting depth interactions as it relates to `xmli` and auto-closing. I added an extra test/example in `asg::graph::visit::test` to illustrate the situation; it was difficult to derive from the traces, but trivially obvious once I wrote it out as an example. With that, templates can now aggregate tokens for dangling expressions. DEV-13708main
parent
286f4cb679
commit
343f5b34b3
|
@ -480,11 +480,13 @@ mod root {
|
|||
|
||||
fn hold_dangling(
|
||||
&self,
|
||||
_asg: &mut Asg,
|
||||
_oi_expr: ObjectIndex<Expr>,
|
||||
asg: &mut Asg,
|
||||
oi_expr: ObjectIndex<Expr>,
|
||||
) -> Result<(), AsgError> {
|
||||
// This will be used for templates.
|
||||
todo!("hold_dangling")
|
||||
let Self(oi_root) = self;
|
||||
|
||||
oi_root.add_edge_to(asg, oi_expr, None);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,6 +229,43 @@ fn tpl_with_reachable_expression() {
|
|||
);
|
||||
}
|
||||
|
||||
// Templates can expand into many contexts,
|
||||
// including other expressions,
|
||||
// and so must be able to contain expressions that,
|
||||
// while dangling now,
|
||||
// will become reachable in its expansion context.
|
||||
#[test]
|
||||
fn tpl_holds_dangling_expressions() {
|
||||
let id_tpl = SPair("_tpl_".into(), S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::TplOpen(S1),
|
||||
Air::BindIdent(id_tpl),
|
||||
|
||||
// Dangling
|
||||
Air::ExprOpen(ExprOp::Sum, S3),
|
||||
Air::ExprClose(S4),
|
||||
|
||||
// Dangling
|
||||
Air::ExprOpen(ExprOp::Sum, S5),
|
||||
Air::ExprClose(S6),
|
||||
Air::TplClose(S7),
|
||||
];
|
||||
|
||||
let asg = asg_from_toks(toks);
|
||||
let oi_tpl = asg.expect_ident_oi::<Tpl>(id_tpl);
|
||||
|
||||
assert_eq!(
|
||||
vec![S5.merge(S6).unwrap(), S3.merge(S4).unwrap(),],
|
||||
oi_tpl
|
||||
.edges_filtered::<Expr>(&asg)
|
||||
.map(ObjectIndex::cresolve(&asg))
|
||||
.map(Expr::span)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_tpl_mid_open() {
|
||||
let id_expr = SPair("expr".into(), S3);
|
||||
|
|
|
@ -32,6 +32,10 @@ use std::fmt::Debug;
|
|||
|
||||
use Air::*;
|
||||
|
||||
// More concise values for tables below.
|
||||
use ObjectRelTy::*;
|
||||
const SU: Span = UNKNOWN_SPAN;
|
||||
|
||||
fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
|
||||
where
|
||||
I::IntoIter: Debug,
|
||||
|
@ -83,10 +87,8 @@ fn traverses_ontological_tree() {
|
|||
let sut = tree_reconstruction(&asg);
|
||||
|
||||
// We need more concise expressions for the below table of values.
|
||||
use ObjectRelTy::*;
|
||||
let d = DynObjectRel::new;
|
||||
let m = |a: Span, b: Span| a.merge(b).unwrap();
|
||||
const SU: Span = UNKNOWN_SPAN;
|
||||
|
||||
// Note that the `Depth` beings at 1 because the actual root of the
|
||||
// graph is not emitted.
|
||||
|
@ -95,7 +97,8 @@ fn traverses_ontological_tree() {
|
|||
// language doesn't have such nesting.
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
vec![
|
||||
// A -|-> B | A span -|-> B span | espan | depth
|
||||
vec![//-----|-------|-----------|-----------|--------|-----------------
|
||||
(d(Root, Pkg, SU, m(S1, S11), None ), Depth(1)),
|
||||
(d(Pkg, Ident, m(S1, S11), S3, None ), Depth(2)),
|
||||
(d(Ident, Expr, S3, m(S2, S7), None ), Depth(3)),
|
||||
|
@ -113,3 +116,80 @@ fn traverses_ontological_tree() {
|
|||
)).collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
// This is a variation of the above test,
|
||||
// focusing on the fact that templates may contain odd constructions that
|
||||
// wouldn't necessarily be valid in other contexts.
|
||||
// This merely establishes a concrete example to re-enforce intuition and
|
||||
// serve as an example of the system's behavior in a laboratory setting,
|
||||
// as opposed to having to scan through real-life traces and all the
|
||||
// complexity and noise therein.
|
||||
//
|
||||
// This also serves as an integration test to ensure that templates produce
|
||||
// the expected result on the graph.
|
||||
// Just as was mentioned above,
|
||||
// that makes this test very fragile,
|
||||
// and you should look at other failing tests before assuming that this
|
||||
// one is broken;
|
||||
// let this help to guide your reasoning of the system rather than
|
||||
// your suspicion.
|
||||
#[test]
|
||||
fn traverses_ontological_tree_tpl_with_sibling_at_increasing_depth() {
|
||||
let id_tpl = SPair("_tpl_".into(), S3);
|
||||
let id_expr = SPair("expr".into(), S7);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
PkgOpen(S1),
|
||||
TplOpen(S2),
|
||||
BindIdent(id_tpl),
|
||||
|
||||
// Dangling
|
||||
ExprOpen(ExprOp::Sum, S4),
|
||||
ExprClose(S5),
|
||||
|
||||
// Reachable
|
||||
ExprOpen(ExprOp::Sum, S6),
|
||||
BindIdent(id_expr),
|
||||
ExprClose(S8),
|
||||
TplClose(S9),
|
||||
PkgClose(S10),
|
||||
];
|
||||
|
||||
let asg = asg_from_toks(toks);
|
||||
let sut = tree_reconstruction(&asg);
|
||||
|
||||
// We need more concise expressions for the below table of values.
|
||||
let d = DynObjectRel::new;
|
||||
let m = |a: Span, b: Span| a.merge(b).unwrap();
|
||||
|
||||
// Writing this example helped to highlight how the system is
|
||||
// functioning and immediately obviated a bug downstream in the
|
||||
// lowering pipeline (xmli derivation) at the time of writing.
|
||||
// The `Tpl->Ident` was ignored along with its `Depth` because it
|
||||
// produced no output,
|
||||
// and therefore the final expression was interpreted as being a
|
||||
// child of its sibling.
|
||||
// This traversal was always correct;
|
||||
// the problem manifested in the integration of these systems and
|
||||
// was caught by system tests.
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
// A -|-> B | A span -|-> B span | espan| depth
|
||||
vec![//-----|-------|-----------|-----------|------|-----------------
|
||||
(d(Root, Pkg, SU, m(S1, S10), None), Depth(1)),
|
||||
(d(Pkg, Ident, m(S1, S10), S3, None), Depth(2)),
|
||||
(d(Ident, Tpl, S3, m(S2, S9), None), Depth(3)),
|
||||
(d(Tpl, Expr, m(S2, S9), m(S4, S5), None), Depth(4)), // --,
|
||||
(d(Tpl, Ident, m(S2, S9), S7, None), Depth(4)), // |
|
||||
(d(Ident, Expr, S7, m(S6, S8), None), Depth(5)), // <'
|
||||
],
|
||||
sut.map(|TreeWalkRel(rel, depth)| (
|
||||
rel.map(|(soi, toi)| (
|
||||
soi.resolve(&asg).span(),
|
||||
toi.resolve(&asg).span()
|
||||
)),
|
||||
depth
|
||||
)).collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ use crate::{
|
|||
xir::{
|
||||
flat::{Text, XirfToken},
|
||||
st::qname::*,
|
||||
OpenSpan, QName,
|
||||
CloseSpan, OpenSpan, QName,
|
||||
},
|
||||
};
|
||||
use arrayvec::ArrayVec;
|
||||
|
@ -179,7 +179,16 @@ impl<'a> TreeContext<'a> {
|
|||
|
||||
// Identifiers will be considered in context;
|
||||
// pass over it for now.
|
||||
Object::Ident(..) => None,
|
||||
// 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::Expr((expr, _)) => {
|
||||
self.emit_expr(expr, paired_rel.source(), depth)
|
||||
|
|
|
@ -44,4 +44,30 @@
|
|||
<any />
|
||||
</classify>
|
||||
</template>
|
||||
|
||||
<template name="_with-static-unreachable_">
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
|
||||
<c:product>
|
||||
<c:sum />
|
||||
</c:product>
|
||||
</template>
|
||||
|
||||
<template name="_with-static-mix-reachability_">
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
|
||||
<c:product>
|
||||
<c:sum />
|
||||
</c:product>
|
||||
|
||||
<rate yields="tplStaticMix" />
|
||||
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
</template>
|
||||
</package>
|
||||
|
|
|
@ -51,4 +51,46 @@
|
|||
<any />
|
||||
</classify>
|
||||
</template>
|
||||
|
||||
<template name="_with-static-unreachable_">
|
||||
This expression is on its own unreachable,
|
||||
intended to be expanded into another expression.
|
||||
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
|
||||
<c:product>
|
||||
<c:sum />
|
||||
</c:product>
|
||||
</template>
|
||||
|
||||
<template name="_with-static-mix-reachability_">
|
||||
Both reachable and unreachable,
|
||||
with the intent of expanding into an expression but also providing
|
||||
itself with supporting expressions.
|
||||
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
|
||||
<c:product> <!-- depth N -->
|
||||
<c:sum /> <!-- depth N+1 -->
|
||||
</c:product>
|
||||
|
||||
The above expression will end at depth N+1,
|
||||
to be auto-closed.
|
||||
The below expression will yield an Ident->Expr,
|
||||
and so will _begin_ at N+1.
|
||||
We must therefore ensure,
|
||||
and this test do so assert,
|
||||
that this matching depth does not cause the reparenting of this next
|
||||
expression into its preceding sibling.
|
||||
|
||||
<rate yields="tplStaticMix" /> <!-- begins at depth N+1 -->
|
||||
|
||||
<c:sum>
|
||||
<c:product />
|
||||
</c:sum>
|
||||
</template>
|
||||
</package>
|
||||
|
|
Loading…
Reference in New Issue