tamer: asg: Move Ident-specific methods off of Asg

Historically, the ASG was better described as a "dependency graph",
containing only identifiers (which are simply called "symbols" in the
XSLT-based compiler).  Consequently, it was appropriate for the graph to
have operations specific to identifiers.  (Indeed, that's the only type of
object the graph supported.)

Much has changed since then.  This cleans things up, and makes parenting
identifiers to root an _explicit_ operation.  This will make it easier to
move forward with handling of scope, and importing identifiers into
packages, and removing `Source`, and so on.

DEV-13162
main
Mike Gerwitz 2023-04-18 14:05:01 -04:00
parent 46551ee298
commit f183600c3a
13 changed files with 728 additions and 1049 deletions

View File

@ -31,7 +31,10 @@ use test::Bencher;
mod base {
use super::*;
use tamer::{
asg::{DefaultAsg, IdentKind, Source},
asg::{
AsgError, DefaultAsg, Ident, IdentKind, ObjectIndex,
ObjectIndexRelTo, Source,
},
parse::util::SPair,
span::UNKNOWN_SPAN,
sym::GlobalSymbolIntern,
@ -39,6 +42,35 @@ mod base {
type Sut = DefaultAsg;
fn declare(
asg: &mut Sut,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result<ObjectIndex<Ident>, AsgError> {
let oi_root = asg.root(name);
oi_root.declare(asg, name, kind, src)
}
fn declare_extern(
asg: &mut Sut,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result<ObjectIndex<Ident>, AsgError> {
lookup_or_missing(asg, name).declare_extern(asg, name, kind, src)
}
fn lookup(asg: &mut Sut, name: SPair) -> Option<ObjectIndex<Ident>> {
let oi_root = asg.root(name);
asg.lookup(oi_root, name)
}
fn lookup_or_missing(asg: &mut Sut, name: SPair) -> ObjectIndex<Ident> {
let oi_root = asg.root(name);
oi_root.lookup_or_missing(asg, name)
}
fn interned_n(n: u16) -> Vec<SPair> {
(0..n)
.map(|i| SPair(i.to_string().intern(), UNKNOWN_SPAN))
@ -52,7 +84,9 @@ mod base {
bench.iter(|| {
xs.iter()
.map(|i| sut.declare(*i, IdentKind::Meta, Source::default()))
.map(|i| {
declare(&mut sut, *i, IdentKind::Meta, Source::default())
})
.for_each(drop);
});
}
@ -64,7 +98,9 @@ mod base {
bench.iter(|| {
xs.iter()
.map(|i| sut.declare(*i, IdentKind::Meta, Source::default()))
.map(|i| {
declare(&mut sut, *i, IdentKind::Meta, Source::default())
})
.for_each(drop);
});
}
@ -76,7 +112,9 @@ mod base {
bench.iter(|| {
xs.iter()
.map(|i| sut.declare(*i, IdentKind::Meta, Source::default()))
.map(|i| {
declare(&mut sut, *i, IdentKind::Meta, Source::default())
})
.for_each(drop);
});
}
@ -89,7 +127,12 @@ mod base {
bench.iter(|| {
xs.iter()
.map(|i| {
sut.declare_extern(*i, IdentKind::Meta, Source::default())
declare_extern(
&mut sut,
*i,
IdentKind::Meta,
Source::default(),
)
})
.for_each(drop);
});
@ -101,15 +144,19 @@ mod base {
let xs = interned_n(1_000);
xs.iter().for_each(|sym| {
let _ =
sut.declare_extern(*sym, IdentKind::Meta, Source::default());
let _ = declare_extern(
&mut sut,
*sym,
IdentKind::Meta,
Source::default(),
);
});
// Bench only the resolution, not initial declare.
bench.iter(|| {
xs.iter()
.map(|sym| {
sut.declare(*sym, IdentKind::Meta, Source::default())
declare(&mut sut, *sym, IdentKind::Meta, Source::default())
})
.for_each(drop);
});
@ -124,14 +171,17 @@ mod base {
let xs = interned_n(1_000);
xs.iter().for_each(|sym| {
sut.declare(*sym, IdentKind::Meta, Source::default())
declare(&mut sut, *sym, IdentKind::Meta, Source::default())
.unwrap();
});
// Bench only the resolution, not initial declare.
bench.iter(|| {
xs.iter()
.map(|sym| sut.set_fragment(*sym, "".into())) // see N.B.
.map(|sym| {
lookup_or_missing(&mut sut, *sym)
.set_fragment(&mut sut, "".into())
}) // see N.B.
.for_each(drop);
});
}
@ -142,12 +192,12 @@ mod base {
let xs = interned_n(1_000);
xs.iter().for_each(|sym| {
let _ = sut.declare(*sym, IdentKind::Meta, Source::default());
let _ = declare(&mut sut, *sym, IdentKind::Meta, Source::default());
});
bench.iter(|| {
xs.iter()
.map(|sym| sut.lookup_global(*sym).unwrap())
.map(|sym| lookup(&mut sut, *sym).unwrap())
.for_each(drop);
});
}
@ -160,7 +210,7 @@ mod base {
let orefs = xs
.iter()
.map(|sym| {
sut.declare(*sym, IdentKind::Meta, Source::default())
declare(&mut sut, *sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
@ -183,7 +233,7 @@ mod base {
let orefs = xs
.iter()
.map(|sym| {
sut.declare(*sym, IdentKind::Meta, Source::default())
declare(&mut sut, *sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
@ -194,7 +244,7 @@ mod base {
bench.iter(|| {
orefs
.iter()
.map(|oref| sut.add_dep(root, *oref))
.map(|oref| root.add_opaque_dep(&mut sut, *oref))
.for_each(drop);
});
}
@ -208,7 +258,7 @@ mod base {
let orefs = xs
.iter()
.map(|sym| {
sut.declare(*sym, IdentKind::Meta, Source::default())
declare(&mut sut, *sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
@ -217,7 +267,7 @@ mod base {
orefs
.iter()
.zip(orefs.iter().cycle().skip(1))
.map(|(from, to)| sut.add_dep(*from, *to))
.map(|(from, to)| from.add_opaque_dep(&mut sut, *to))
.for_each(drop);
});
}
@ -230,7 +280,7 @@ mod base {
let orefs = xs
.iter()
.map(|sym| {
sut.declare(*sym, IdentKind::Meta, Source::default())
declare(&mut sut, *sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
@ -238,13 +288,13 @@ mod base {
let root = orefs[0];
orefs.iter().for_each(|oref| {
sut.add_dep(root, *oref);
root.add_opaque_dep(&mut sut, *oref);
});
bench.iter(|| {
orefs
.iter()
.map(|oref| sut.has_dep(root, *oref))
.map(|oref| root.has_edge_to(&sut, *oref))
.for_each(drop);
});
}
@ -258,14 +308,14 @@ mod base {
let orefs = xs
.iter()
.map(|sym| {
sut.declare(*sym, IdentKind::Meta, Source::default())
declare(&mut sut, *sym, IdentKind::Meta, Source::default())
.unwrap()
})
.collect::<Vec<_>>();
orefs.iter().zip(orefs.iter().cycle().skip(1)).for_each(
|(from, to)| {
sut.add_dep(*from, *to);
from.add_opaque_dep(&mut sut, *to);
},
);
@ -273,37 +323,7 @@ mod base {
orefs
.iter()
.zip(orefs.iter().cycle().skip(1))
.map(|(from, to)| sut.has_dep(*from, *to))
.for_each(drop);
});
}
#[bench]
fn add_dep_lookup_1_000_missing_one_edge_per_node(bench: &mut Bencher) {
let mut sut = Sut::new();
let xs = interned_n(1_000);
bench.iter(|| {
xs.iter()
.zip(xs.iter().cycle().skip(1))
.map(|(from, to)| sut.add_dep_lookup_global(*from, *to))
.for_each(drop);
});
}
#[bench]
fn add_dep_lookup_1_000_existing_one_edge_per_node(bench: &mut Bencher) {
let mut sut = Sut::new();
let xs = interned_n(1_000);
xs.iter().for_each(|sym| {
let _ = sut.declare(*sym, IdentKind::Meta, Source::default());
});
bench.iter(|| {
xs.iter()
.zip(xs.iter().cycle().skip(1))
.map(|(from, to)| sut.add_dep_lookup_global(*from, *to))
.map(|(from, to)| from.has_edge_to(&sut, *to))
.for_each(drop);
});
}

View File

@ -25,7 +25,7 @@ extern crate test;
use test::Bencher;
pub(crate) use tamer::{
asg::{DefaultAsg, IdentKind, Source},
asg::{AsgError, DefaultAsg, Ident, IdentKind, ObjectIndex, Source},
ld::xmle::{lower::sort, Sections},
num::Dtype,
parse::util::SPair,
@ -41,6 +41,16 @@ fn interned_n(n: u16) -> Vec<SPair> {
.collect()
}
fn declare(
asg: &mut TestAsg,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result<ObjectIndex<Ident>, AsgError> {
let oi_root = asg.root(name);
oi_root.declare(asg, name, kind, src)
}
#[bench]
fn sort_1_with_1_000_existing_supernode(bench: &mut Bencher) {
let mut sut = TestAsg::new();
@ -49,7 +59,8 @@ fn sort_1_with_1_000_existing_supernode(bench: &mut Bencher) {
let orefs = xs
.iter()
.map(|sym| {
sut.declare(
declare(
&mut sut,
*sym,
IdentKind::Rate(Dtype::Integer),
Source::default(),
@ -62,10 +73,10 @@ fn sort_1_with_1_000_existing_supernode(bench: &mut Bencher) {
// All edges from a single node.
orefs.iter().skip(1).for_each(|to| {
sut.add_dep(root, *to);
root.add_opaque_dep(&mut sut, *to);
});
sut.add_root(root);
root.root(&mut sut);
bench.iter(|| {
drop(sort(&sut, Sections::new()));
@ -80,7 +91,8 @@ fn sort_1_with_1_000_existing_one_edge_per_node_one_path(bench: &mut Bencher) {
let orefs = xs
.iter()
.map(|sym| {
sut.declare(
declare(
&mut sut,
*sym,
IdentKind::Rate(Dtype::Integer),
Source::default(),
@ -96,12 +108,12 @@ fn sort_1_with_1_000_existing_one_edge_per_node_one_path(bench: &mut Bencher) {
.iter()
.zip(orefs.iter().skip(1))
.for_each(|(from, to)| {
sut.add_dep(*from, *to);
from.add_opaque_dep(&mut sut, *to);
});
let root = orefs[0];
sut.add_root(root);
root.root(&mut sut);
bench.iter(|| {
drop(sort(&sut, Sections::new()));

View File

@ -232,33 +232,50 @@ impl ParseState for AirAggregate {
tok @ (AirExpr(..) | AirBind(..) | AirTpl(..) | AirDoc(..)),
) => Transition(Empty).err(AsgError::PkgExpected(tok.span())),
(Empty, AirIdent(IdentDecl(name, kind, src))) => ctx
.asg_mut()
.declare(name, kind, src)
.map(|_| ())
.transition(Empty),
(Empty, AirIdent(IdentDecl(name, kind, src))) => {
let asg = ctx.asg_mut();
let oi_root = asg.root(name);
(Empty, AirIdent(IdentExternDecl(name, kind, src))) => ctx
.asg_mut()
.declare_extern(name, kind, src)
.map(|_| ())
.transition(Empty),
asg.lookup_or_missing(oi_root, name)
.declare(asg, name, kind, src)
.map(|_| ())
.transition(Empty)
}
(Empty, AirIdent(IdentExternDecl(name, kind, src))) => {
let asg = ctx.asg_mut();
let oi_root = asg.root(name);
asg.lookup_or_missing(oi_root, name)
.declare_extern(asg, name, kind, src)
.map(|_| ())
.transition(Empty)
}
(Empty, AirIdent(IdentDep(name, dep))) => {
let asg = ctx.asg_mut();
let oi_root = asg.root(dep);
let oi_from = asg.lookup_or_missing(oi_root, name);
let oi_to = asg.lookup_or_missing(oi_root, dep);
oi_from.add_opaque_dep(ctx.asg_mut(), oi_to);
(Empty, AirIdent(IdentDep(sym, dep))) => {
ctx.asg_mut().add_dep_lookup_global(sym, dep);
Transition(Empty).incomplete()
}
(Empty, AirIdent(IdentFragment(sym, text))) => ctx
.asg_mut()
.set_fragment(sym, text)
.map(|_| ())
.transition(Empty),
(Empty, AirIdent(IdentRoot(sym))) => {
(Empty, AirIdent(IdentFragment(name, text))) => {
let asg = ctx.asg_mut();
let obj = asg.lookup_global_or_missing(sym);
asg.add_root(obj);
let oi_root = asg.root(name);
asg.lookup_or_missing(oi_root, name)
.set_fragment(asg, text)
.map(|_| ())
.transition(Empty)
}
(Empty, AirIdent(IdentRoot(name))) => {
let asg = ctx.asg_mut();
asg.root(name).root_ident(asg, name);
Transition(Empty).incomplete()
}

View File

@ -24,7 +24,7 @@ use super::{super::Ident, *};
use crate::{
asg::{
graph::object::{ObjectRel, ObjectRelFrom, ObjectRelatable},
IdentKind, Source,
IdentKind, ObjectIndexRelTo, Source,
},
parse::{ParseError, Parsed, Parser},
span::dummy::*,
@ -49,9 +49,8 @@ fn ident_decl() {
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup_global(id)
.expect("identifier was not added to graph");
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to graph");
let ident = asg.get(ident_node).unwrap();
assert_eq!(
@ -90,9 +89,8 @@ fn ident_extern_decl() {
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup_global(id)
.expect("identifier was not added to graph");
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to graph");
let ident = asg.get(ident_node).unwrap();
assert_eq!(
@ -129,12 +127,11 @@ fn ident_dep() {
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup_global(id)
.expect("identifier was not added to graph");
let dep_node = asg.lookup_global(dep).expect("dep was not added to graph");
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to graph");
let dep_node = root_lookup(&asg, dep).expect("dep was not added to graph");
assert!(asg.has_dep(ident_node, dep_node));
assert!(ident_node.has_edge_to(&asg, dep_node));
}
#[test]
@ -162,9 +159,8 @@ fn ident_fragment() {
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup_global(id)
.expect("identifier was not added to graph");
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to graph");
let ident = asg.get(ident_node).unwrap();
assert_eq!(
@ -199,9 +195,8 @@ fn ident_root_missing() {
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup_global(id)
.expect("identifier was not added to the graph");
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to the graph");
let ident = asg.get(ident_node).unwrap();
// The identifier did not previously exist,
@ -209,7 +204,7 @@ fn ident_root_missing() {
assert_eq!(&Ident::Missing(id), ident);
// And that missing identifier should be rooted.
assert!(asg.is_rooted(ident_node));
assert!(ident_node.is_rooted(&asg));
}
#[test]
@ -238,9 +233,8 @@ fn ident_root_existing() {
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup_global(id)
.expect("identifier was not added to the graph");
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to the graph");
let ident = asg.get(ident_node).unwrap();
// The previously-declared identifier...
@ -252,7 +246,41 @@ fn ident_root_existing() {
);
// ...should have been subsequently rooted.
assert!(asg.is_rooted(ident_node));
assert!(ident_node.is_rooted(&asg));
}
#[test]
fn declare_kind_auto_root() {
let auto_kind = IdentKind::Worksheet;
let no_auto_kind = IdentKind::Tpl;
// Sanity check, in case this changes.
assert!(auto_kind.is_auto_root());
assert!(!no_auto_kind.is_auto_root());
let id_auto = SPair("auto_root".into(), S1);
let id_no_auto = SPair("no_auto_root".into(), S2);
let toks = [
// auto-rooting
Air::IdentDecl(id_auto, auto_kind, Default::default()),
// non-auto-rooting
Air::IdentDecl(id_no_auto, no_auto_kind, Default::default()),
]
.into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
let asg = sut.finalize().unwrap().into_context();
let oi_auto = root_lookup(&asg, id_auto).unwrap();
let oi_no_auto = root_lookup(&asg, id_no_auto).unwrap();
assert!(oi_auto.is_rooted(&asg));
assert!(!oi_no_auto.is_rooted(&asg));
}
#[test]
@ -403,6 +431,10 @@ where
sut.finalize().unwrap().into_context()
}
fn root_lookup(asg: &Asg, name: SPair) -> Option<ObjectIndex<Ident>> {
asg.lookup(asg.root(S1), name)
}
pub fn pkg_lookup(asg: &Asg, name: SPair) -> Option<ObjectIndex<Ident>> {
let oi_pkg = asg
.root(S1)

View File

@ -26,10 +26,7 @@ use self::object::{
ObjectRelFrom, ObjectRelTy, ObjectRelatable, Root,
};
use super::{
AsgError, FragmentText, Ident, IdentKind, Object, ObjectIndex, ObjectKind,
Source, TransitionResult,
};
use super::{AsgError, Ident, Object, ObjectIndex, ObjectKind};
use crate::{
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
f::Functor,
@ -50,7 +47,7 @@ pub mod object;
pub mod visit;
pub mod xmli;
use object::{ObjectContainer, ObjectRelTo};
use object::ObjectContainer;
/// Datatype representing node and edge indexes.
pub trait IndexType = petgraph::graph::IndexType;
@ -282,64 +279,6 @@ impl Asg {
})
}
/// Lookup `ident` or add a missing identifier to the graph relative to
/// the global scope and return a reference to it.
///
/// See [`Self::lookup_or_missing`] for more information.
pub(super) fn lookup_global_or_missing(
&mut self,
name: SPair,
) -> ObjectIndex<Ident> {
self.lookup_or_missing(
ObjectIndex::<Root>::new(self.root_node, name),
name,
)
}
/// Perform a state transition on an identifier by name.
///
/// Look up `ident` or add a missing identifier if it does not yet exist
/// (see [`Self::lookup_global_or_missing`]).
/// Then invoke `f` with the located identifier and replace the
/// identifier on the graph with the result.
///
/// This will safely restore graph state to the original identifier
/// value on transition failure.
fn with_ident_lookup_global<F>(
&mut self,
name: SPair,
f: F,
) -> AsgResult<ObjectIndex<Ident>>
where
F: FnOnce(Ident) -> TransitionResult<Ident>,
{
let identi = self.lookup_global_or_missing(name);
self.with_ident(identi, f)
}
/// Perform a state transition on an identifier by [`ObjectIndex`].
///
/// Invoke `f` with the located identifier and replace the identifier on
/// the graph with the result.
///
/// This will safely restore graph state to the original identifier
/// value on transition failure.
fn with_ident<F>(
&mut self,
identi: ObjectIndex<Ident>,
f: F,
) -> AsgResult<ObjectIndex<Ident>>
where
F: FnOnce(Ident) -> TransitionResult<Ident>,
{
let container = self.graph.node_weight_mut(identi.into()).unwrap();
container
.try_replace_with(f)
.map(|()| identi)
.map_err(Into::into)
}
/// Root object.
///
/// All [`Object`]s reachable from the root will be included in the
@ -348,127 +287,8 @@ impl Asg {
/// The `witness` is used in the returned [`ObjectIndex`] and is
/// intended for diagnostic purposes to highlight the source entity that
/// triggered the request of the root.
pub fn root(&self, witness: Span) -> ObjectIndex<Root> {
ObjectIndex::new(self.root_node, witness)
}
/// Add an object as a root.
///
/// Roots are always included during a topological sort and any
/// reachability analysis.
///
/// Ideally,
/// roots would be minimal and dependencies properly organized such
/// that objects will be included if they are a transitive dependency
/// of some included subsystem.
///
/// See also [`IdentKind::is_auto_root`].
pub fn add_root(&mut self, identi: ObjectIndex<Ident>) {
self.graph.add_edge(
self.root_node,
identi.into(),
(ObjectRelTy::Root, ObjectRelTy::Ident, None),
);
}
/// Whether an object is rooted.
///
/// See [`Asg::add_root`] for more information about roots.
#[cfg(test)]
pub(super) fn is_rooted(&self, identi: ObjectIndex<Ident>) -> bool {
self.graph.contains_edge(self.root_node, identi.into())
}
/// Declare a concrete identifier.
///
/// An identifier declaration is similar to a declaration in a header
/// file in a language like C,
/// describing the structure of the identifier.
/// Once declared,
/// this information cannot be changed.
///
/// Identifiers are uniquely identified by a [`SymbolId`] `name`.
/// If an identifier of the same `name` already exists,
/// then the provided declaration is compared against the existing
/// declaration---should
/// they be incompatible,
/// then the operation will fail;
/// otherwise,
/// the existing identifier will be returned.
///
/// If a concrete identifier has already been declared (see
/// [`Asg::declare`]),
/// then extern declarations will be compared and,
/// if compatible,
/// the identifier will be immediately _resolved_ and the object
/// on the graph will not be altered.
/// Resolution will otherwise fail in error.
///
/// For more information on state transitions that can occur when
/// redeclaring an identifier that already exists,
/// see [`Ident::resolve`].
///
/// A successful declaration will add an identifier to the graph
/// and return an [`ObjectIndex`] reference.
pub fn declare(
&mut self,
name: SPair,
kind: IdentKind,
src: Source,
) -> AsgResult<ObjectIndex<Ident>> {
let is_auto_root = kind.is_auto_root();
self.with_ident_lookup_global(name, |obj| {
obj.resolve(name.span(), kind, src)
})
.map(|node| {
is_auto_root.then(|| self.add_root(node));
node
})
}
/// Declare an abstract identifier.
///
/// An _extern_ declaration declares an identifier the same as
/// [`Asg::declare`],
/// but omits source information.
/// Externs are identifiers that are expected to be defined somewhere
/// else ("externally"),
/// and are resolved at [link-time][crate::ld].
///
/// If a concrete identifier has already been declared (see
/// [`Asg::declare`]),
/// then the declarations will be compared and,
/// if compatible,
/// the identifier will be immediately _resolved_ and the object
/// on the graph will not be altered.
/// Resolution will otherwise fail in error.
///
/// See [`Ident::extern_`] and
/// [`Ident::resolve`] for more information on
/// compatibility related to extern resolution.
pub fn declare_extern(
&mut self,
name: SPair,
kind: IdentKind,
src: Source,
) -> AsgResult<ObjectIndex<Ident>> {
self.with_ident_lookup_global(name, |obj| {
obj.extern_(name.span(), kind, src)
})
}
/// Set the fragment associated with a concrete identifier.
///
/// Fragments are intended for use by the [linker][crate::ld].
/// For more information,
/// see [`Ident::set_fragment`].
pub fn set_fragment(
&mut self,
name: SPair,
text: FragmentText,
) -> AsgResult<ObjectIndex<Ident>> {
self.with_ident_lookup_global(name, |obj| obj.set_fragment(text))
pub fn root<S: Into<Span>>(&self, witness: S) -> ObjectIndex<Root> {
ObjectIndex::new(self.root_node, witness.into())
}
/// Create a new object on the graph.
@ -492,7 +312,7 @@ impl Asg {
/// _reference_ to the target.
///
/// For more information on how the ASG's ontology is enforced statically,
/// see [`ObjectRelTo`].
/// see [`ObjectRelTo`](object::ObjectRelTo).
fn add_edge<OB: ObjectKind + ObjectRelatable>(
&mut self,
from_oi: impl ObjectIndexRelTo<OB>,
@ -571,7 +391,7 @@ impl Asg {
/// what is intended.
/// This is sufficient in practice,
/// since the graph cannot be constructed without adhering to the edge
/// ontology defined by [`ObjectRelTo`],
/// ontology defined by [`ObjectRelTo`](object::ObjectRelTo),
/// but this API is not helpful for catching problems at
/// compile-time.
///
@ -648,6 +468,16 @@ impl Asg {
.map(move |edge| ObjectIndex::<OI>::new(edge.source(), oi))
}
/// Check whether an edge exists from `from` to `to.
#[inline]
pub fn has_edge<OB: ObjectRelatable>(
&self,
from: impl ObjectIndexRelTo<OB>,
to: ObjectIndex<OB>,
) -> bool {
self.graph.contains_edge(from.widen().into(), to.into())
}
pub(super) fn expect_obj<O: ObjectKind>(&self, oi: ObjectIndex<O>) -> &O {
let obj_container =
self.graph.node_weight(oi.into()).diagnostic_expect(
@ -658,15 +488,6 @@ impl Asg {
obj_container.get()
}
/// Retrieve an identifier from the graph by [`ObjectIndex`].
///
/// If the object exists but is not an identifier,
/// [`None`] will be returned.
#[inline]
pub fn get_ident(&self, index: ObjectIndex<Ident>) -> Option<&Ident> {
self.get(index)
}
/// Attempt to retrieve an identifier from the graph by name relative to
/// the immediate environment `imm_env`.
///
@ -679,7 +500,7 @@ impl Asg {
/// compilation unit,
/// which is a package.
#[inline]
pub(super) fn lookup<OS: ObjectIndexTreeRelTo<Ident>>(
pub fn lookup<OS: ObjectIndexTreeRelTo<Ident>>(
&self,
imm_env: OS,
id: SPair,
@ -688,85 +509,6 @@ impl Asg {
.get(&(id.symbol(), imm_env.into()))
.map(|&ni| ni.overwrite(id.span()))
}
/// Attempt to retrieve an identifier from the graph by name utilizing
/// the global environment.
///
/// Since only identifiers carry a name,
/// this method cannot be used to retrieve all possible objects on the
/// graph---for
/// that, see [`Asg::get`].
///
/// The global environment is defined as the environment of the current
/// compilation unit,
/// which is a package.
#[inline]
pub fn lookup_global(&self, name: SPair) -> Option<ObjectIndex<Ident>> {
self.lookup(ObjectIndex::<Root>::new(self.root_node, name), name)
}
/// Declare that `dep` is a dependency of `ident`.
///
/// An object must be declared as a dependency if its value must be
/// computed before computing the value of `ident`.
/// The [linker][crate::ld] will ensure this ordering.
///
/// See [`Self::add_dep_lookup_global`] if identifiers have to
/// be looked up by [`SymbolId`] or if they may not yet have been
/// declared.
pub fn add_dep<O: ObjectKind>(
&mut self,
identi: ObjectIndex<Ident>,
depi: ObjectIndex<O>,
) where
Ident: ObjectRelTo<O>,
{
self.graph.update_edge(
identi.into(),
depi.into(),
(Ident::rel_ty(), O::rel_ty(), None),
);
}
/// Check whether `dep` is a dependency of `ident`.
#[inline]
pub fn has_dep(
&self,
ident: ObjectIndex<Ident>,
dep: ObjectIndex<Ident>,
) -> bool {
self.graph.contains_edge(ident.into(), dep.into())
}
/// Declare that `dep` is a dependency of `ident`,
/// regardless of whether they are known.
///
/// In contrast to [`add_dep`][Asg::add_dep],
/// this method will add the dependency even if one or both of `ident`
/// or `dep` have not yet been declared.
/// In such a case,
/// a missing identifier will be added as a placeholder,
/// allowing the ASG to be built with partial information as
/// identifiers continue to be discovered.
/// See [`Ident::declare`] for more information.
///
/// References to both identifiers are returned in argument order.
pub fn add_dep_lookup_global(
&mut self,
ident: SPair,
dep: SPair,
) -> (ObjectIndex<Ident>, ObjectIndex<Ident>) {
let identi = self.lookup_global_or_missing(ident);
let depi = self.lookup_global_or_missing(dep);
self.graph.update_edge(
identi.into(),
depi.into(),
(Ident::rel_ty(), Ident::rel_ty(), None),
);
(identi, depi)
}
}
fn diagnostic_node_missing_desc<O: ObjectKind>(

View File

@ -677,7 +677,7 @@ impl<O: ObjectKind> ObjectIndex<O> {
.filter(|_| O::rel_ty() == OB::rel_ty())
}
/// Root this object in the ASG.
/// Root this object in the ASG's [`Root`] object.
///
/// A rooted object is forced to be reachable.
/// This should only be utilized when necessary for toplevel objects;
@ -693,6 +693,16 @@ impl<O: ObjectKind> ObjectIndex<O> {
self
}
/// Whether this object has been rooted in the ASG's [`Root`] object.
///
/// See [`Self::root`] for more information.
pub fn is_rooted(&self, asg: &Asg) -> bool
where
Root: ObjectRelTo<O>,
{
asg.root(self.span()).has_edge_to(asg, *self)
}
/// Widen an [`ObjectKind`] `O` into [`Object`],
/// generalizing the index type.
///

View File

@ -983,6 +983,67 @@ object_rel! {
}
impl ObjectIndex<Ident> {
/// Declare a concrete identifier.
///
/// An identifier declaration is similar to a declaration in a header
/// file in a language like C,
/// describing the structure of the identifier.
/// Once declared,
/// this information cannot be changed.
///
/// Identifiers are uniquely identified by a [`SPair`] `name`.
/// If an identifier of the same `name` already exists,
/// then the provided declaration is compared against the existing
/// declaration---should
/// they be incompatible,
/// then the operation will fail;
/// otherwise,
/// the existing identifier will be returned.
///
/// If a concrete identifier has already been declared,
/// then extern declarations will be compared and,
/// if compatible,
/// the identifier will be immediately _resolved_ and the object
/// on the graph will not be altered.
/// Resolution will otherwise fail in error.
///
/// For more information on state transitions that can occur when
/// redeclaring an identifier that already exists,
/// see [`Ident::resolve`].
///
/// A successful declaration will add an identifier to the graph
/// and return an [`ObjectIndex`] reference.
pub fn declare(
self,
asg: &mut Asg,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result<ObjectIndex<Ident>, AsgError> {
let is_auto_root = kind.is_auto_root();
self.try_map_obj(asg, |obj| obj.resolve(name.span(), kind, src))
.map_err(Into::into)
.map(|ident| {
is_auto_root.then(|| self.root(asg));
ident
})
}
/// Declare an abstract identifier.
///
/// See [`Ident::extern_`] and [`Ident::resolve`] for more information.
pub fn declare_extern(
self,
asg: &mut Asg,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result<Self, AsgError> {
self.try_map_obj(asg, |obj| obj.extern_(name.span(), kind, src))
.map_err(Into::into)
}
/// Bind an identifier to a `definition`,
/// making it [`Transparent`].
///
@ -1042,6 +1103,20 @@ impl ObjectIndex<Ident> {
.map(|ident_oi| ident_oi.add_edge_to(asg, definition, None))
}
/// Set the fragment associated with a concrete identifier.
///
/// Fragments are intended for use by the [linker][crate::ld].
/// For more information,
/// see [`Ident::set_fragment`].
pub fn set_fragment(
self,
asg: &mut Asg,
text: SymbolId,
) -> Result<Self, AsgError> {
self.try_map_obj(asg, |obj| obj.set_fragment(text))
.map_err(Into::into)
}
/// Look up the definition that this identifier binds to,
/// if any.
///
@ -1078,6 +1153,15 @@ impl ObjectIndex<Ident> {
pub fn src_pkg(&self, asg: &Asg) -> Option<ObjectIndex<Pkg>> {
self.incoming_edges_filtered(asg).next()
}
/// Declare that `oi_dep` is an opaque dependency of `self`.
pub fn add_opaque_dep(
&self,
asg: &mut Asg,
oi_dep: ObjectIndex<Ident>,
) -> Self {
self.add_edge_to(asg, oi_dep, None)
}
}
#[cfg(test)]

View File

@ -663,6 +663,11 @@ pub trait ObjectIndexRelTo<OB: ObjectRelatable>: Sized + Clone + Copy {
self
}
/// Check whether an edge exists from `self` to `to_oi`.
fn has_edge_to(&self, asg: &Asg, to_oi: ObjectIndex<OB>) -> bool {
asg.has_edge(*self, to_oi)
}
/// Indicate that the given identifier `oi` is defined by this object.
fn defines(self, asg: &mut Asg, oi: ObjectIndex<Ident>) -> Self
where
@ -708,7 +713,7 @@ pub trait ObjectIndexRelTo<OB: ObjectRelatable>: Sized + Clone + Copy {
/// if you've arrived at this method while investigating unfavorable
/// circumstances during profiling,
/// then you should consider caching like the global environment
/// (see [`Asg::lookup_global`]).
/// (see [`Asg::lookup`]).
fn lookup_local_linear(
&self,
asg: &Asg,

View File

@ -19,9 +19,12 @@
//! Root node of the ASG.
use std::fmt::Display;
use super::{prelude::*, Ident, Pkg};
use crate::{
asg::{IdentKind, Source},
parse::util::SPair,
};
use std::fmt::Display;
#[cfg(doc)]
use super::ObjectKind;
@ -46,3 +49,40 @@ object_rel! {
tree Ident,
}
}
impl ObjectIndex<Root> {
/// Root an identifier in the graph without a parent [`Pkg`].
///
/// An identifier ought to be rooted by a package that defines it;
/// this is intended as a legacy operation for `tameld` that will be
/// removed in the future.
pub fn root_ident(&self, asg: &mut Asg, name: SPair) -> ObjectIndex<Ident> {
asg.lookup_or_missing(*self, name)
.add_edge_from(asg, *self, None)
}
/// Attempt to retrieve an indexed [`Ident`] owned by `self`.
///
/// See [`Self::root_ident`].
pub fn lookup_or_missing(
&self,
asg: &mut Asg,
name: SPair,
) -> ObjectIndex<Ident> {
asg.lookup_or_missing(*self, name)
}
/// Declare a concrete identifier.
///
/// See [`ObjectIndex::<Ident>::declare`] for more information.
pub fn declare(
&self,
asg: &mut Asg,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result<ObjectIndex<Ident>, AsgError> {
self.lookup_or_missing(asg, name)
.declare(asg, name, kind, src)
}
}

View File

@ -17,10 +17,9 @@
// 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::super::error::AsgError;
use super::*;
use crate::{num::Dim, span::dummy::*, sym::GlobalSymbolIntern};
use std::{assert_matches::assert_matches, convert::Infallible};
use crate::span::dummy::*;
use std::convert::Infallible;
type Sut = Asg;
@ -36,333 +35,6 @@ fn create_with_capacity() {
assert!(sut.index.capacity() >= node_capacity);
}
#[test]
fn declare_new_unique_idents() -> AsgResult<()> {
let mut sut = Sut::new();
// NB: The index ordering is important! We first use a larger
// index to create a gap, and then use an index within that gap
// to ensure that it's not considered an already-defined
// identifier.
let syma = "syma".into();
let symb = "symab".into();
let nodea = sut.declare(
SPair(syma, S1),
IdentKind::Meta,
Source {
desc: Some("a".into()),
..Default::default()
},
)?;
let nodeb = sut.declare(
SPair(symb, S2),
IdentKind::Worksheet,
Source {
desc: Some("b".into()),
..Default::default()
},
)?;
assert_ne!(nodea, nodeb);
let givena = sut.get_ident(nodea).unwrap();
assert_eq!(SPair(syma, S1), givena.name());
assert_eq!(Some(&IdentKind::Meta), givena.kind());
assert_eq!(
Some(&Source {
desc: Some("a".into()),
..Default::default()
},),
givena.src()
);
let givenb = sut.get_ident(nodeb).unwrap();
assert_eq!(SPair(symb, S2), givenb.name());
assert_eq!(Some(&IdentKind::Worksheet), givenb.kind());
assert_eq!(
Some(&Source {
desc: Some("b".into()),
..Default::default()
}),
givenb.src()
);
Ok(())
}
#[test]
fn declare_kind_auto_root() -> AsgResult<()> {
let mut sut = Sut::new();
let auto_kind = IdentKind::Worksheet;
// Sanity check, in case this changes.
assert!(auto_kind.is_auto_root());
let auto_root_node = sut.declare(
SPair("auto_root".into(), S1),
auto_kind,
Default::default(),
)?;
// Should have been automatically added as a root.
assert!(sut
.graph
.contains_edge(sut.root_node, auto_root_node.into()));
let no_auto_kind = IdentKind::Tpl;
assert!(!no_auto_kind.is_auto_root());
let no_auto_root_node = sut.declare(
SPair("no_auto_root".into(), S2),
no_auto_kind,
Default::default(),
)?;
// Non-auto-roots should _not_ be added as roots automatically.
assert!(!sut
.graph
.contains_edge(sut.root_node, no_auto_root_node.into()));
Ok(())
}
#[test]
fn lookup_by_symbol() -> AsgResult<()> {
let mut sut = Sut::new();
let id = SPair("lookup".into(), S1);
let node = sut.declare(
id,
IdentKind::Meta,
Source {
generated: true,
..Default::default()
},
)?;
assert_eq!(Some(node), sut.lookup_global(id));
Ok(())
}
#[test]
fn declare_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symdup".into();
let src = Source {
desc: Some("orig".into()),
..Default::default()
};
// Set up an object to fail redeclaration.
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
let result =
sut.declare(SPair(sym, S2), IdentKind::Meta, Source::default());
assert_matches!(result, Err(AsgError::IdentTransition(..)));
// The node should have been restored.
assert_eq!(Some(&src), sut.get_ident(node).unwrap().src());
Ok(())
}
#[test]
fn declare_extern_returns_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symext".into();
let src = Source::default();
let kind = IdentKind::Class(Dim::Matrix);
let node = sut.declare_extern(SPair(sym, S1), kind.clone(), src.clone())?;
let resrc = Source {
desc: Some("redeclare".into()),
..Default::default()
};
let redeclare =
sut.declare_extern(SPair(sym, S2), kind.clone(), resrc.clone())?;
assert_eq!(node, redeclare);
Ok(())
}
// Builds upon declare_returns_existing.
#[test]
fn declare_extern_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symdup".into();
let src = Source {
desc: Some("orig".into()),
..Default::default()
};
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
// Changes kind, which is invalid.
let result = sut.declare_extern(
SPair(sym, S2),
IdentKind::Worksheet,
Source::default(),
);
assert_matches!(result, Err(AsgError::IdentTransition(..)));
// The node should have been restored.
assert_eq!(Some(&src), sut.get_ident(node).unwrap().src());
Ok(())
}
#[test]
fn add_fragment_to_ident() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "tofrag".into();
let src = Source {
generated: true,
..Default::default()
};
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
let fragment = "a fragment".intern();
let node_with_frag = sut.set_fragment(SPair(sym, S2), fragment)?;
// Attaching a fragment should _replace_ the node, not create a
// new one
assert_eq!(
node, node_with_frag,
"fragment node does not match original node"
);
let obj = sut.get_ident(node).unwrap();
assert_eq!(SPair(sym, S1), obj.name());
assert_eq!(Some(&IdentKind::Meta), obj.kind());
assert_eq!(Some(&src), obj.src());
assert_eq!(Some(fragment), obj.fragment());
Ok(())
}
#[test]
fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "failfrag".into();
let src = Source {
generated: true,
..Default::default()
};
// The failure will come from terr below, not this.
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
// The first set will succeed.
sut.set_fragment(SPair(sym, S2), "".into())?;
// This will fail.
let result = sut.set_fragment(SPair(sym, S3), "".into());
// The node should have been restored.
let obj = sut.get_ident(node).unwrap();
assert_eq!(SPair(sym, S1), obj.name());
assert_matches!(result, Err(AsgError::IdentTransition(..)));
Ok(())
}
#[test]
fn add_ident_dep_to_ident() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "sym".into();
let dep = "dep".into();
let symnode =
sut.declare(SPair(sym, S1), IdentKind::Meta, Source::default())?;
let depnode =
sut.declare(SPair(dep, S2), IdentKind::Meta, Source::default())?;
sut.add_dep(symnode, depnode);
assert!(sut.has_dep(symnode, depnode));
// sanity check if we re-add a dep
sut.add_dep(symnode, depnode);
assert!(sut.has_dep(symnode, depnode));
Ok(())
}
// same as above test
#[test]
fn add_dep_lookup_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let _ = sut.declare(sym, IdentKind::Meta, Source::default())?;
let _ = sut.declare(dep, IdentKind::Meta, Source::default())?;
let (symnode, depnode) = sut.add_dep_lookup_global(sym, dep);
assert!(sut.has_dep(symnode, depnode));
Ok(())
}
#[test]
fn add_dep_lookup_missing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
// both of these are missing
let (symnode, depnode) = sut.add_dep_lookup_global(sym, dep);
assert!(sut.has_dep(symnode, depnode));
assert_eq!(sym, sut.get_ident(symnode).unwrap().name());
assert_eq!(dep, sut.get_ident(depnode).unwrap().name());
Ok(())
}
#[test]
fn declare_return_missing_symbol() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
// both of these are missing, see add_dep_lookup_missing
let (symnode, _) = sut.add_dep_lookup_global(sym, dep);
let src = Source {
desc: Some("redeclare missing".into()),
..Default::default()
};
// Check with a declared value
let declared = sut.declare(sym, IdentKind::Meta, src.clone())?;
assert_eq!(symnode, declared);
let obj = sut.get_ident(declared).unwrap();
assert_eq!(sym, obj.name());
assert_eq!(Some(&IdentKind::Meta), obj.kind());
assert_eq!(Some(&src), obj.src());
Ok(())
}
#[test]
fn try_map_narrows_and_modifies() {
let mut sut = Sut::new();

View File

@ -70,7 +70,7 @@ pub use graph::{
FragmentText, Ident, IdentKind, Source, TransitionError,
TransitionResult, UnresolvedError,
},
Object, ObjectIndex, ObjectKind,
Object, ObjectIndex, ObjectIndexRelTo, ObjectKind,
},
visit,
xmli::AsgTreeToXirf,

View File

@ -94,10 +94,11 @@ fn get_ident<S>(depgraph: &Asg, name: S) -> &Ident
where
S: Into<SymbolId>,
{
let oi_root = depgraph.root(UNKNOWN_SPAN);
let sym = name.into();
depgraph
.lookup_global(SPair(sym, UNKNOWN_SPAN))
.lookup(oi_root, SPair(sym, UNKNOWN_SPAN))
.and_then(|id| depgraph.get(id))
.unwrap_or_else(|| {
panic!("missing internal identifier: {}", sym.lookup_str())

View File

@ -19,7 +19,7 @@
use super::*;
use crate::{
asg::{FragmentText, Ident, Source},
asg::{AsgError, FragmentText, Ident, Source},
ld::xmle::{section::PushResult, Sections},
num::{Dim, Dtype},
parse::util::SPair,
@ -27,6 +27,21 @@ use crate::{
sym::GlobalSymbolIntern,
};
fn declare(
asg: &mut Asg,
name: SPair,
kind: IdentKind,
src: Source,
) -> Result<ObjectIndex<Ident>, AsgError> {
let oi_root = asg.root(name);
oi_root.declare(asg, name, kind, src)
}
fn lookup_or_missing(asg: &mut Asg, name: SPair) -> ObjectIndex<Ident> {
let oi_root = asg.root(name);
oi_root.lookup_or_missing(asg, name)
}
/// Create a graph with the expected {ret,}map head/tail identifiers.
fn make_asg() -> Asg {
let mut asg = Asg::new();
@ -35,30 +50,34 @@ fn make_asg() -> Asg {
{
let sym = SPair(st::L_MAP_UUUHEAD.into(), S1);
asg.declare(sym, IdentKind::MapHead, Default::default())
declare(&mut asg, sym, IdentKind::MapHead, Default::default())
.unwrap()
.set_fragment(&mut asg, text)
.unwrap();
asg.set_fragment(sym, text).unwrap();
}
{
let sym = SPair(st::L_MAP_UUUTAIL.into(), S2);
asg.declare(sym, IdentKind::MapTail, Default::default())
declare(&mut asg, sym, IdentKind::MapTail, Default::default())
.unwrap()
.set_fragment(&mut asg, text)
.unwrap();
asg.set_fragment(sym, text).unwrap();
}
{
let sym = SPair(st::L_RETMAP_UUUHEAD.into(), S3);
asg.declare(sym, IdentKind::RetMapHead, Default::default())
declare(&mut asg, sym, IdentKind::RetMapHead, Default::default())
.unwrap()
.set_fragment(&mut asg, text)
.unwrap();
asg.set_fragment(sym, text).unwrap();
}
{
let sym = SPair(st::L_RETMAP_UUUTAIL.into(), S4);
asg.declare(sym, IdentKind::RetMapTail, Default::default())
declare(&mut asg, sym, IdentKind::RetMapTail, Default::default())
.unwrap()
.set_fragment(&mut asg, text)
.unwrap();
asg.set_fragment(sym, text).unwrap();
}
asg
@ -106,28 +125,32 @@ fn graph_sort() -> SortResult<()> {
let mut asg = make_asg();
// Add them in an unsorted order.
let adep = asg
.declare(
SPair("adep".into(), S1),
IdentKind::Meta,
Default::default(),
)
.unwrap();
let a = asg
.declare(SPair("a".into(), S2), IdentKind::Meta, Default::default())
.unwrap();
let adepdep = asg
.declare(
SPair("adepdep".into(), S3),
IdentKind::Meta,
Default::default(),
)
.unwrap();
let adep = declare(
&mut asg,
SPair("adep".into(), S1),
IdentKind::Meta,
Default::default(),
)
.unwrap();
let a = declare(
&mut asg,
SPair("a".into(), S2),
IdentKind::Meta,
Default::default(),
)
.unwrap();
let adepdep = declare(
&mut asg,
SPair("adepdep".into(), S3),
IdentKind::Meta,
Default::default(),
)
.unwrap();
asg.add_dep(a, adep);
asg.add_dep(adep, adepdep);
a.add_opaque_dep(&mut asg, adep);
adep.add_opaque_dep(&mut asg, adepdep);
asg.add_root(a);
a.root(&mut asg);
let sections = sort(&asg, StubSections { pushed: Vec::new() })?;
@ -138,9 +161,9 @@ fn graph_sort() -> SortResult<()> {
get_ident(&asg, st::L_MAP_UUUHEAD),
get_ident(&asg, st::L_RETMAP_UUUHEAD),
// Post-order
asg.get_ident(adepdep).unwrap(),
asg.get_ident(adep).unwrap(),
asg.get_ident(a).unwrap(),
asg.get(adepdep).unwrap(),
asg.get(adep).unwrap(),
asg.get(a).unwrap(),
// Static tail
get_ident(&asg, st::L_MAP_UUUTAIL),
get_ident(&asg, st::L_RETMAP_UUUTAIL),
@ -159,22 +182,22 @@ fn graph_sort_missing_node() -> SortResult<()> {
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let oi_dep = lookup_or_missing(&mut asg, dep);
asg.set_fragment(sym, FragmentText::from("foo")).unwrap();
let (_, _) = asg.add_dep_lookup_global(sym, dep);
asg.add_root(sym_node);
declare(
&mut asg,
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap()
.add_opaque_dep(&mut asg, oi_dep)
.root(&mut asg);
match sort(&asg, Sections::new()) {
Ok(_) => panic!("Unexpected success - dependency is not in graph"),
@ -192,7 +215,7 @@ fn graph_sort_missing_node() -> SortResult<()> {
#[test]
fn graph_sort_no_roots_same_as_empty_graph() -> SortResult<()> {
let mut asg_nonempty_no_roots = make_asg();
let mut asg = make_asg();
// "empty" (it has the head/tail {ret,}map objects)
let asg_empty = make_asg();
@ -200,10 +223,12 @@ fn graph_sort_no_roots_same_as_empty_graph() -> SortResult<()> {
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
asg_nonempty_no_roots.add_dep_lookup_global(sym, dep);
let oi_sym = lookup_or_missing(&mut asg, sym);
let oi_dep = lookup_or_missing(&mut asg, dep);
oi_sym.add_opaque_dep(&mut asg, oi_dep);
assert_eq!(
sort(&asg_nonempty_no_roots, Sections::new()),
sort(&asg, Sections::new()),
sort(&asg_empty, Sections::new())
);
@ -217,39 +242,40 @@ fn graph_sort_simple_cycle() -> SortResult<()> {
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let oi_sym = declare(
&mut asg,
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
let dep_node = asg
.declare(
dep,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let oi_dep = declare(
&mut asg,
dep,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
asg.set_fragment(sym, FragmentText::from("foo")).unwrap();
asg.set_fragment(dep, FragmentText::from("bar")).unwrap();
oi_sym.add_opaque_dep(&mut asg, oi_dep);
oi_dep.add_opaque_dep(&mut asg, oi_sym);
let (_, _) = asg.add_dep_lookup_global(sym, dep);
let (_, _) = asg.add_dep_lookup_global(dep, sym);
asg.add_root(sym_node);
oi_sym.root(&mut asg);
let result = sort(&asg, Sections::new());
let expected = vec![[dep_node, sym_node]
let expected = vec![[oi_dep, oi_sym]
.into_iter()
.map(|o| asg.get(o).unwrap().name())
.collect::<Vec<_>>()];
@ -272,65 +298,68 @@ fn graph_sort_two_simple_cycles() -> SortResult<()> {
let dep = SPair("dep".into(), S3);
let dep2 = SPair("dep2".into(), S4);
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let oi_sym = declare(
&mut asg,
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
let sym2_node = asg
.declare(
sym2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let oi_sym2 = declare(
&mut asg,
sym2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
let dep_node = asg
.declare(
dep,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let oi_dep = declare(
&mut asg,
dep,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("baz"))
.unwrap();
let dep2_node = asg
.declare(
dep2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let oi_dep2 = declare(
&mut asg,
dep2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("huh"))
.unwrap();
asg.set_fragment(sym, FragmentText::from("foo")).unwrap();
asg.set_fragment(sym2, FragmentText::from("bar")).unwrap();
asg.set_fragment(dep, FragmentText::from("baz")).unwrap();
asg.set_fragment(dep2, FragmentText::from("huh")).unwrap();
oi_sym.add_opaque_dep(&mut asg, oi_dep);
oi_dep.add_opaque_dep(&mut asg, oi_sym);
oi_sym2.add_opaque_dep(&mut asg, oi_dep2);
oi_dep2.add_opaque_dep(&mut asg, oi_sym2);
let (_, _) = asg.add_dep_lookup_global(sym, dep);
let (_, _) = asg.add_dep_lookup_global(dep, sym);
let (_, _) = asg.add_dep_lookup_global(sym2, dep2);
let (_, _) = asg.add_dep_lookup_global(dep2, sym2);
asg.add_root(sym_node);
oi_sym.root(&mut asg);
let result = sort(&asg, Sections::new());
let expected = [[dep_node, sym_node], [dep2_node, sym2_node]]
let expected = [[oi_dep, oi_sym], [oi_dep2, oi_sym2]]
.into_iter()
.map(|cycle| {
cycle
@ -356,18 +385,21 @@ fn graph_sort_no_cycle_with_edge_to_same_node() -> SortResult<()> {
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym_node = declare(
&mut asg,
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
asg.declare(
let dep_node = declare(
&mut asg,
dep,
IdentKind::Tpl,
Source {
@ -375,15 +407,14 @@ fn graph_sort_no_cycle_with_edge_to_same_node() -> SortResult<()> {
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
asg.set_fragment(sym, FragmentText::from("foo")).unwrap();
asg.set_fragment(dep, FragmentText::from("bar")).unwrap();
sym_node.add_opaque_dep(&mut asg, dep_node);
sym_node.add_opaque_dep(&mut asg, dep_node);
let (_, _) = asg.add_dep_lookup_global(sym, dep);
let (_, _) = asg.add_dep_lookup_global(sym, dep);
asg.add_root(sym_node);
sym_node.root(&mut asg);
let result = sort(&asg, Sections::new());
@ -403,48 +434,50 @@ fn graph_sort_cycle_with_a_few_steps() -> SortResult<()> {
let sym2 = SPair("sym2".into(), S2);
let sym3 = SPair("sym3".into(), S3);
let sym1_node = asg
.declare(
sym1,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym1_node = declare(
&mut asg,
sym1,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
let sym2_node = asg
.declare(
sym2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym2_node = declare(
&mut asg,
sym2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
let sym3_node = asg
.declare(
sym3,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym3_node = declare(
&mut asg,
sym3,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("baz"))
.unwrap();
asg.set_fragment(sym1, FragmentText::from("foo")).unwrap();
asg.set_fragment(sym2, FragmentText::from("bar")).unwrap();
asg.set_fragment(sym3, FragmentText::from("baz")).unwrap();
sym1_node.add_opaque_dep(&mut asg, sym2_node);
sym2_node.add_opaque_dep(&mut asg, sym3_node);
sym3_node.add_opaque_dep(&mut asg, sym1_node);
let (_, _) = asg.add_dep_lookup_global(sym1, sym2);
let (_, _) = asg.add_dep_lookup_global(sym2, sym3);
let (_, _) = asg.add_dep_lookup_global(sym3, sym1);
asg.add_root(sym1_node);
sym1_node.root(&mut asg);
let result = sort(&asg, Sections::new());
@ -471,48 +504,50 @@ fn graph_sort_cyclic_function_with_non_function_with_a_few_steps(
let sym2 = SPair("sym2".into(), S2);
let sym3 = SPair("sym3".into(), S3);
let sym1_node = asg
.declare(
sym1,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym1_node = declare(
&mut asg,
sym1,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
let sym2_node = asg
.declare(
sym2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym2_node = declare(
&mut asg,
sym2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
let sym3_node = asg
.declare(
sym3,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym3_node = declare(
&mut asg,
sym3,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("baz"))
.unwrap();
asg.set_fragment(sym1, FragmentText::from("foo")).unwrap();
asg.set_fragment(sym2, FragmentText::from("bar")).unwrap();
asg.set_fragment(sym3, FragmentText::from("baz")).unwrap();
sym1_node.add_opaque_dep(&mut asg, sym2_node);
sym2_node.add_opaque_dep(&mut asg, sym3_node);
sym3_node.add_opaque_dep(&mut asg, sym1_node);
let (_, _) = asg.add_dep_lookup_global(sym1, sym2);
let (_, _) = asg.add_dep_lookup_global(sym2, sym3);
let (_, _) = asg.add_dep_lookup_global(sym3, sym1);
asg.add_root(sym1_node);
sym1_node.root(&mut asg);
let result = sort(&asg, Sections::new());
@ -538,48 +573,50 @@ fn graph_sort_cyclic_bookended_by_functions() -> SortResult<()> {
let sym2 = SPair("sym2".into(), S2);
let sym3 = SPair("sym3".into(), S3);
let sym1_node = asg
.declare(
sym1,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym1_node = declare(
&mut asg,
sym1,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
let sym2_node = asg
.declare(
sym2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym2_node = declare(
&mut asg,
sym2,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
let sym3_node = asg
.declare(
sym3,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym3_node = declare(
&mut asg,
sym3,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("baz"))
.unwrap();
asg.set_fragment(sym1, FragmentText::from("foo")).unwrap();
asg.set_fragment(sym2, FragmentText::from("bar")).unwrap();
asg.set_fragment(sym3, FragmentText::from("baz")).unwrap();
sym1_node.add_opaque_dep(&mut asg, sym2_node);
sym2_node.add_opaque_dep(&mut asg, sym3_node);
sym3_node.add_opaque_dep(&mut asg, sym1_node);
let (_, _) = asg.add_dep_lookup_global(sym1, sym2);
let (_, _) = asg.add_dep_lookup_global(sym2, sym3);
let (_, _) = asg.add_dep_lookup_global(sym3, sym1);
asg.add_root(sym1_node);
sym1_node.root(&mut asg);
let result = sort(&asg, Sections::new());
@ -604,18 +641,21 @@ fn graph_sort_cyclic_function_ignored() -> SortResult<()> {
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let sym_node = asg
.declare(
sym,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym_node = declare(
&mut asg,
sym,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
asg.declare(
let dep_node = declare(
&mut asg,
dep,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
@ -623,15 +663,14 @@ fn graph_sort_cyclic_function_ignored() -> SortResult<()> {
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
asg.set_fragment(sym, FragmentText::from("foo")).unwrap();
asg.set_fragment(dep, FragmentText::from("bar")).unwrap();
sym_node.add_opaque_dep(&mut asg, dep_node);
dep_node.add_opaque_dep(&mut asg, sym_node);
let (_, _) = asg.add_dep_lookup_global(sym, dep);
let (_, _) = asg.add_dep_lookup_global(dep, sym);
asg.add_root(sym_node);
sym_node.root(&mut asg);
let result = sort(&asg, Sections::new());
@ -651,48 +690,50 @@ fn graph_sort_cyclic_function_is_bookended() -> SortResult<()> {
let sym2 = SPair("sym2".into(), S2);
let sym3 = SPair("sym3".into(), S3);
let sym1_node = asg
.declare(
sym1,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym1_node = declare(
&mut asg,
sym1,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
let sym2_node = asg
.declare(
sym2,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym2_node = declare(
&mut asg,
sym2,
IdentKind::Func(Dim::Scalar, Dtype::Empty),
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
let sym3_node = asg
.declare(
sym3,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym3_node = declare(
&mut asg,
sym3,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("baz"))
.unwrap();
asg.set_fragment(sym1, FragmentText::from("foo")).unwrap();
asg.set_fragment(sym2, FragmentText::from("bar")).unwrap();
asg.set_fragment(sym3, FragmentText::from("baz")).unwrap();
sym1_node.add_opaque_dep(&mut asg, sym2_node);
sym2_node.add_opaque_dep(&mut asg, sym3_node);
sym3_node.add_opaque_dep(&mut asg, sym1_node);
let (_, _) = asg.add_dep_lookup_global(sym1, sym2);
let (_, _) = asg.add_dep_lookup_global(sym2, sym3);
let (_, _) = asg.add_dep_lookup_global(sym3, sym1);
asg.add_root(sym1_node);
sym1_node.root(&mut asg);
let result = sort(&asg, Sections::new());
@ -718,18 +759,21 @@ fn graph_sort_ignore_non_linked() -> SortResult<()> {
let dep = SPair("dep".into(), S2);
let ignored = SPair("ignored".into(), S3);
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap();
let sym_node = declare(
&mut asg,
sym,
IdentKind::Tpl,
Source {
virtual_: true,
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("foo"))
.unwrap();
asg.declare(
let dep_node = declare(
&mut asg,
dep,
IdentKind::Tpl,
Source {
@ -737,9 +781,12 @@ fn graph_sort_ignore_non_linked() -> SortResult<()> {
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("bar"))
.unwrap();
asg.declare(
let ignored_node = declare(
&mut asg,
ignored,
IdentKind::Tpl,
Source {
@ -747,17 +794,14 @@ fn graph_sort_ignore_non_linked() -> SortResult<()> {
..Default::default()
},
)
.unwrap()
.set_fragment(&mut asg, FragmentText::from("baz"))
.unwrap();
asg.set_fragment(sym, FragmentText::from("foo")).unwrap();
asg.set_fragment(dep, FragmentText::from("bar")).unwrap();
asg.set_fragment(ignored, FragmentText::from("baz"))
.unwrap();
sym_node.add_opaque_dep(&mut asg, dep_node);
ignored_node.add_opaque_dep(&mut asg, sym_node);
let (_, _) = asg.add_dep_lookup_global(sym, dep);
let (_, _) = asg.add_dep_lookup_global(ignored, sym);
asg.add_root(sym_node);
sym_node.root(&mut asg);
let result = sort(&asg, Sections::new());