2023-02-07 14:59:36 -05:00
|
|
|
// ASG IR
|
|
|
|
//
|
|
|
|
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
|
|
|
|
//
|
|
|
|
// This file is part of TAME.
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
use crate::{
|
|
|
|
asg::{
|
|
|
|
air::{Air, AirAggregate},
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
graph::object::ObjectRelTy,
|
2023-02-07 14:59:36 -05:00
|
|
|
ExprOp,
|
|
|
|
},
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
f::Functor,
|
2023-02-07 14:59:36 -05:00
|
|
|
parse::{util::SPair, ParseState},
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
span::{dummy::*, Span},
|
2023-02-07 14:59:36 -05:00
|
|
|
};
|
|
|
|
use std::fmt::Debug;
|
|
|
|
|
|
|
|
use Air::*;
|
|
|
|
|
2023-03-08 23:44:40 -05:00
|
|
|
// More concise values for tables below.
|
|
|
|
use ObjectRelTy::*;
|
|
|
|
const SU: Span = UNKNOWN_SPAN;
|
|
|
|
|
2023-02-07 14:59:36 -05:00
|
|
|
fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
|
|
|
|
where
|
|
|
|
I::IntoIter: Debug,
|
|
|
|
{
|
|
|
|
let mut parser = AirAggregate::parse(toks.into_iter());
|
|
|
|
assert!(parser.all(|x| x.is_ok()));
|
|
|
|
parser.finalize().unwrap().into_context()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note that this is an integration test beginning at AIR.
|
2023-03-06 12:03:55 -05:00
|
|
|
// It is therefore fragile,
|
|
|
|
// and will break if any of these complex systems fail in subtle ways.
|
|
|
|
// If you arrived at this test without having modified the visitor,
|
|
|
|
// then it's quite possible that you should be looking elsewhere.
|
2023-02-07 14:59:36 -05:00
|
|
|
//
|
|
|
|
// We will construct a test ASG using the same subsystem as the user;
|
|
|
|
// we want to be sure that the traversal works as we expect it to in
|
|
|
|
// practice,
|
|
|
|
// since the system is fairly complex and failures are more likely
|
|
|
|
// to occur at integration points.
|
|
|
|
#[test]
|
|
|
|
fn traverses_ontological_tree() {
|
|
|
|
let id_a = SPair("expr_a".into(), S3);
|
|
|
|
let id_b = SPair("expr_b".into(), S9);
|
|
|
|
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
#[rustfmt::skip]
|
2023-02-07 14:59:36 -05:00
|
|
|
let toks = vec![
|
|
|
|
PkgOpen(S1),
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
ExprOpen(ExprOp::Sum, S2),
|
2023-02-28 11:31:06 -05:00
|
|
|
BindIdent(id_a),
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
|
|
|
ExprOpen(ExprOp::Sum, S4),
|
|
|
|
ExprClose(S5),
|
|
|
|
|
2023-03-06 15:17:02 -05:00
|
|
|
RefIdent(SPair(id_b.symbol(), S6)),
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
ExprClose(S7),
|
|
|
|
|
|
|
|
ExprOpen(ExprOp::Sum, S8),
|
2023-02-28 11:31:06 -05:00
|
|
|
BindIdent(id_b),
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
ExprClose(S10),
|
2023-02-07 14:59:36 -05:00
|
|
|
PkgClose(S11),
|
|
|
|
];
|
|
|
|
|
|
|
|
let asg = asg_from_toks(toks);
|
|
|
|
|
|
|
|
// From the above graph,
|
|
|
|
// we're going to traverse in such a way as to reconstruct the source
|
|
|
|
// tree.
|
|
|
|
let sut = tree_reconstruction(&asg);
|
|
|
|
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
// 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();
|
|
|
|
|
2023-02-07 16:43:40 -05:00
|
|
|
// Note that the `Depth` beings at 1 because the actual root of the
|
|
|
|
// graph is not emitted.
|
|
|
|
// Further note that the depth is the depth of the _path_,
|
|
|
|
// and so identifiers contribute to the depth even though the source
|
|
|
|
// language doesn't have such nesting.
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
#[rustfmt::skip]
|
2023-02-07 14:59:36 -05:00
|
|
|
assert_eq!(
|
2023-03-08 23:44:40 -05:00
|
|
|
// A -|-> B | A span -|-> B span | espan | depth
|
|
|
|
vec![//-----|-------|-----------|-----------|--------|-----------------
|
tamer: asg::graph::object::rel::DynObjectRel: Store source data
This is generic over the source, just as the target, defaulting just the
same to `ObjectIndex`.
This allows us to use only the edge information provided rather than having
to perform another lookup on the graph and then assert that we found the
correct edge. In this case, we're dealing with an `Ident->Expr` edge, of
which there is only one, but in other cases, there may be many such edges,
and it wouldn't be possible to know _which_ was referred to without also
keeping context of the previous edge in the walk.
So, in addition to avoiding more indirection and being more immune to logic
bugs, this also allows us to avoid states in `AsgTreeToXirf` for the purpose
of tracking previous edges in the current path. And it means that the tree
walk can seed further traversals in conjunction with it, if that is so
needed for deriving sources.
More cleanup will be needed, but this does well to set us up for moving
forward; I was too uncomfortable with having to do the separate
lookup. This is also a more intuitive API.
But it does have the awkward effect that now I don't need the pair---I just
need the `Object`---but I'm not going to remove it because I suspect I may
need it in the future. We'll see.
The TODO references the fact that I'm using a convenient `resolve_oi_pairs`
instead of resolving only the target first and then the source only in the
code path that needs it. I'll want to verify that Rust will properly
optimize to avoid the source resolution in branches that do not need it.
DEV-13708
2023-02-23 22:45:09 -05:00
|
|
|
(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)),
|
|
|
|
(d(Expr, Expr, m(S2, S7), m(S4, S5), None ), Depth(4)),
|
|
|
|
(d(Expr, Ident, m(S2, S7), S9, Some(S6)), Depth(4)),
|
|
|
|
(d(Pkg, Ident, m(S1, S11), S9, None ), Depth(2)),
|
|
|
|
(d(Ident, Expr, S9, m(S8, S10), None ), Depth(3)),
|
2023-02-07 14:59:36 -05:00
|
|
|
],
|
tamer: asg::graph::object::rel::DynObjectRel: Store source data
This is generic over the source, just as the target, defaulting just the
same to `ObjectIndex`.
This allows us to use only the edge information provided rather than having
to perform another lookup on the graph and then assert that we found the
correct edge. In this case, we're dealing with an `Ident->Expr` edge, of
which there is only one, but in other cases, there may be many such edges,
and it wouldn't be possible to know _which_ was referred to without also
keeping context of the previous edge in the walk.
So, in addition to avoiding more indirection and being more immune to logic
bugs, this also allows us to avoid states in `AsgTreeToXirf` for the purpose
of tracking previous edges in the current path. And it means that the tree
walk can seed further traversals in conjunction with it, if that is so
needed for deriving sources.
More cleanup will be needed, but this does well to set us up for moving
forward; I was too uncomfortable with having to do the separate
lookup. This is also a more intuitive API.
But it does have the awkward effect that now I don't need the pair---I just
need the `Object`---but I'm not going to remove it because I suspect I may
need it in the future. We'll see.
The TODO references the fact that I'm using a convenient `resolve_oi_pairs`
instead of resolving only the target first and then the source only in the
code path that needs it. I'll want to verify that Rust will properly
optimize to avoid the source resolution in branches that do not need it.
DEV-13708
2023-02-23 22:45:09 -05:00
|
|
|
sut.map(|TreeWalkRel(rel, depth)| (
|
|
|
|
rel.map(|(soi, toi)| (
|
|
|
|
soi.resolve(&asg).span(),
|
|
|
|
toi.resolve(&asg).span()
|
|
|
|
)),
|
|
|
|
depth
|
|
|
|
)).collect::<Vec<_>>(),
|
2023-02-07 14:59:36 -05:00
|
|
|
);
|
|
|
|
}
|
2023-03-08 23:44:40 -05:00
|
|
|
|
|
|
|
// 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<_>>(),
|
|
|
|
);
|
|
|
|
}
|