2022-12-13 13:57:04 -05:00
|
|
|
// Tests for ASG IR
|
|
|
|
//
|
2023-01-17 23:09:25 -05:00
|
|
|
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
|
2022-12-13 13:57:04 -05:00
|
|
|
//
|
|
|
|
// 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/>.
|
|
|
|
|
|
|
|
//! These are tested as if they are another API directly atop of the ASG,
|
|
|
|
//! since that is how they are used.
|
|
|
|
|
2023-03-07 14:00:46 -05:00
|
|
|
use super::{super::Ident, *};
|
2022-12-13 13:57:04 -05:00
|
|
|
use crate::{
|
2023-03-07 14:00:46 -05:00
|
|
|
asg::{IdentKind, Source},
|
2023-03-07 13:35:01 -05:00
|
|
|
parse::{ParseError, Parsed, Parser},
|
2022-12-15 12:07:58 -05:00
|
|
|
span::dummy::*,
|
2022-12-13 13:57:04 -05:00
|
|
|
};
|
2022-12-22 16:32:21 -05:00
|
|
|
use std::assert_matches::assert_matches;
|
2022-12-13 13:57:04 -05:00
|
|
|
|
|
|
|
type Sut = AirAggregate;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_decl() {
|
2023-01-17 14:42:43 -05:00
|
|
|
let id = SPair("foo".into(), S1);
|
2022-12-13 13:57:04 -05:00
|
|
|
let kind = IdentKind::Tpl;
|
|
|
|
let src = Source {
|
|
|
|
src: Some("test/decl".into()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
2023-01-17 14:42:43 -05:00
|
|
|
let toks = vec![Air::IdentDecl(id, kind.clone(), src.clone())].into_iter();
|
2022-12-13 13:57:04 -05:00
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
2023-01-17 14:42:43 -05:00
|
|
|
let ident_node = asg.lookup(id).expect("identifier was not added to graph");
|
2022-12-13 13:57:04 -05:00
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Ok(ident),
|
2023-01-17 14:42:43 -05:00
|
|
|
Ident::declare(id)
|
2022-12-15 12:07:58 -05:00
|
|
|
.resolve(S1, kind.clone(), src.clone())
|
2022-12-13 13:57:04 -05:00
|
|
|
.as_ref(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Re-instantiate the parser and test an error by attempting to
|
|
|
|
// redeclare the same identifier.
|
2023-01-17 14:42:43 -05:00
|
|
|
let bad_toks =
|
|
|
|
vec![Air::IdentDecl(SPair(id.symbol(), S2), kind, src)].into_iter();
|
2022-12-13 13:57:04 -05:00
|
|
|
let mut sut = Sut::parse_with_context(bad_toks, asg);
|
|
|
|
|
|
|
|
assert_matches!(
|
|
|
|
sut.next(),
|
|
|
|
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_extern_decl() {
|
2023-01-17 14:42:43 -05:00
|
|
|
let id = SPair("foo".into(), S1);
|
2022-12-13 13:57:04 -05:00
|
|
|
let kind = IdentKind::Tpl;
|
|
|
|
let src = Source {
|
|
|
|
src: Some("test/decl-extern".into()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
2023-01-17 14:42:43 -05:00
|
|
|
let toks =
|
|
|
|
vec![Air::IdentExternDecl(id, kind.clone(), src.clone())].into_iter();
|
2022-12-13 13:57:04 -05:00
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
2023-01-17 14:42:43 -05:00
|
|
|
let ident_node = asg.lookup(id).expect("identifier was not added to graph");
|
2022-12-13 13:57:04 -05:00
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Ok(ident),
|
2023-01-17 14:42:43 -05:00
|
|
|
Ident::declare(id).extern_(S1, kind, src.clone()).as_ref(),
|
2022-12-13 13:57:04 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
// Re-instantiate the parser and test an error by attempting to
|
|
|
|
// redeclare with a different kind.
|
|
|
|
let different_kind = IdentKind::Meta;
|
2023-01-17 14:42:43 -05:00
|
|
|
let bad_toks = vec![Air::IdentExternDecl(
|
|
|
|
SPair(id.symbol(), S2),
|
|
|
|
different_kind,
|
|
|
|
src,
|
|
|
|
)]
|
|
|
|
.into_iter();
|
2022-12-13 13:57:04 -05:00
|
|
|
let mut sut = Sut::parse_with_context(bad_toks, asg);
|
|
|
|
|
|
|
|
assert_matches!(
|
|
|
|
sut.next(),
|
|
|
|
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_dep() {
|
2023-01-17 14:42:43 -05:00
|
|
|
let id = SPair("foo".into(), S1);
|
|
|
|
let dep = SPair("dep".into(), S2);
|
2022-12-13 13:57:04 -05:00
|
|
|
|
2023-01-17 14:42:43 -05:00
|
|
|
let toks = vec![Air::IdentDep(id, dep)].into_iter();
|
2022-12-13 13:57:04 -05:00
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
2023-01-17 14:42:43 -05:00
|
|
|
let ident_node = asg.lookup(id).expect("identifier was not added to graph");
|
2022-12-13 13:57:04 -05:00
|
|
|
let dep_node = asg.lookup(dep).expect("dep was not added to graph");
|
|
|
|
|
|
|
|
assert!(asg.has_dep(ident_node, dep_node));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_fragment() {
|
2023-01-17 14:42:43 -05:00
|
|
|
let id = SPair("frag".into(), S1);
|
2022-12-13 13:57:04 -05:00
|
|
|
let kind = IdentKind::Tpl;
|
|
|
|
let src = Source {
|
|
|
|
src: Some("test/frag".into()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let frag = "fragment text".into();
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
// Identifier must be declared before it can be given a
|
|
|
|
// fragment.
|
2023-01-17 14:42:43 -05:00
|
|
|
Air::IdentDecl(id, kind.clone(), src.clone()),
|
|
|
|
Air::IdentFragment(id, frag),
|
2022-12-13 13:57:04 -05:00
|
|
|
]
|
|
|
|
.into_iter();
|
|
|
|
|
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentDecl
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentFragment
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
2023-01-17 14:42:43 -05:00
|
|
|
let ident_node = asg.lookup(id).expect("identifier was not added to graph");
|
2022-12-13 13:57:04 -05:00
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Ok(ident),
|
2023-01-17 14:42:43 -05:00
|
|
|
Ident::declare(id)
|
2022-12-15 12:07:58 -05:00
|
|
|
.resolve(S1, kind.clone(), src.clone())
|
2022-12-13 13:57:04 -05:00
|
|
|
.and_then(|resolved| resolved.set_fragment(frag))
|
|
|
|
.as_ref(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Re-instantiate the parser and test an error by attempting to
|
|
|
|
// re-set the fragment.
|
2023-01-17 14:42:43 -05:00
|
|
|
let bad_toks = vec![Air::IdentFragment(id, frag)].into_iter();
|
2022-12-13 13:57:04 -05:00
|
|
|
let mut sut = Sut::parse_with_context(bad_toks, asg);
|
|
|
|
|
|
|
|
assert_matches!(
|
|
|
|
sut.next(),
|
|
|
|
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adding a root before the identifier exists should add a
|
|
|
|
// `Ident::Missing`.
|
|
|
|
#[test]
|
|
|
|
fn ident_root_missing() {
|
2023-01-17 14:42:43 -05:00
|
|
|
let id = SPair("toroot".into(), S1);
|
2022-12-13 13:57:04 -05:00
|
|
|
|
2023-01-17 14:42:43 -05:00
|
|
|
let toks = vec![Air::IdentRoot(id)].into_iter();
|
2022-12-13 13:57:04 -05:00
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
let ident_node = asg
|
2023-01-17 14:42:43 -05:00
|
|
|
.lookup(id)
|
2022-12-13 13:57:04 -05:00
|
|
|
.expect("identifier was not added to the graph");
|
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
// The identifier did not previously exist,
|
|
|
|
// and so a missing node is created as a placeholder.
|
2023-01-17 14:42:43 -05:00
|
|
|
assert_eq!(&Ident::Missing(id), ident);
|
2022-12-13 13:57:04 -05:00
|
|
|
|
|
|
|
// And that missing identifier should be rooted.
|
|
|
|
assert!(asg.is_rooted(ident_node));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_root_existing() {
|
2023-01-17 14:42:43 -05:00
|
|
|
let id = SPair("toroot".into(), S1);
|
2022-12-13 13:57:04 -05:00
|
|
|
let kind = IdentKind::Tpl;
|
|
|
|
let src = Source {
|
|
|
|
src: Some("test/root-existing".into()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Ensure that it won't auto-root based on the kind,
|
|
|
|
// otherwise we won't be testing the right thing.
|
|
|
|
assert!(!kind.is_auto_root());
|
|
|
|
|
|
|
|
let toks = vec![
|
2023-01-17 14:42:43 -05:00
|
|
|
Air::IdentDecl(id, kind.clone(), src.clone()),
|
|
|
|
Air::IdentRoot(SPair(id.symbol(), S2)),
|
2022-12-13 13:57:04 -05:00
|
|
|
]
|
|
|
|
.into_iter();
|
|
|
|
|
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentDecl
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentRoot
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
let ident_node = asg
|
2023-01-17 14:42:43 -05:00
|
|
|
.lookup(id)
|
2022-12-13 13:57:04 -05:00
|
|
|
.expect("identifier was not added to the graph");
|
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
// The previously-declared identifier...
|
|
|
|
assert_eq!(
|
|
|
|
Ok(ident),
|
2023-01-17 14:42:43 -05:00
|
|
|
Ident::declare(id)
|
2022-12-15 12:07:58 -05:00
|
|
|
.resolve(S1, kind.clone(), src.clone())
|
2022-12-13 13:57:04 -05:00
|
|
|
.as_ref()
|
|
|
|
);
|
|
|
|
|
|
|
|
// ...should have been subsequently rooted.
|
|
|
|
assert!(asg.is_rooted(ident_node));
|
|
|
|
}
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
#[test]
|
2023-01-31 22:00:51 -05:00
|
|
|
fn pkg_is_rooted() {
|
2023-03-15 10:59:22 -04:00
|
|
|
let toks = vec![Air::PkgStart(S1), Air::PkgEnd(S2)];
|
2023-01-30 16:51:24 -05:00
|
|
|
|
|
|
|
let mut sut = Sut::parse(toks.into_iter());
|
|
|
|
assert!(sut.all(|x| x.is_ok()));
|
|
|
|
|
2023-01-31 22:00:51 -05:00
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
let oi_root = asg.root(S3);
|
|
|
|
let pkg = oi_root
|
|
|
|
.edges_filtered::<Pkg>(&asg)
|
|
|
|
.next()
|
|
|
|
.expect("missing rooted package")
|
|
|
|
.resolve(&asg);
|
|
|
|
|
2023-02-07 12:19:27 -05:00
|
|
|
assert_eq!(pkg.span(), S1.merge(S2).unwrap());
|
2023-01-30 16:51:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn close_pkg_without_open() {
|
|
|
|
let toks = vec![
|
2023-03-15 10:59:22 -04:00
|
|
|
Air::PkgEnd(S1),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY: Try again.
|
2023-03-15 10:59:22 -04:00
|
|
|
Air::PkgStart(S2),
|
|
|
|
Air::PkgEnd(S3),
|
2023-01-30 16:51:24 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-03-15 10:59:22 -04:00
|
|
|
Err(ParseError::StateError(AsgError::InvalidPkgEndContext(S1))),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY
|
2023-03-15 10:59:22 -04:00
|
|
|
Ok(Parsed::Incomplete), // PkgStart
|
|
|
|
Ok(Parsed::Incomplete), // PkgEnd
|
2023-01-30 16:51:24 -05:00
|
|
|
],
|
|
|
|
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nested_open_pkg() {
|
|
|
|
let toks = vec![
|
2023-03-15 10:59:22 -04:00
|
|
|
Air::PkgStart(S1),
|
|
|
|
Air::PkgStart(S2),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY
|
2023-03-15 10:59:22 -04:00
|
|
|
Air::PkgEnd(S3),
|
2023-01-30 16:51:24 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-03-15 10:59:22 -04:00
|
|
|
Ok(Parsed::Incomplete), // PkgStart
|
|
|
|
Err(ParseError::StateError(AsgError::NestedPkgStart(S2, S1))),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY
|
2023-03-15 10:59:22 -04:00
|
|
|
Ok(Parsed::Incomplete), // PkgEnd
|
2023-01-30 16:51:24 -05:00
|
|
|
],
|
|
|
|
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse using [`Sut`] when the test does not care about the outer package.
|
2023-03-07 13:35:01 -05:00
|
|
|
pub fn parse_as_pkg_body<I: IntoIterator<Item = Air>>(
|
2023-01-30 16:51:24 -05:00
|
|
|
toks: I,
|
|
|
|
) -> Parser<Sut, impl Iterator<Item = Air> + Debug>
|
|
|
|
where
|
|
|
|
<I as IntoIterator>::IntoIter: Debug,
|
|
|
|
{
|
|
|
|
use std::iter;
|
|
|
|
|
|
|
|
Sut::parse(
|
2023-03-15 10:59:22 -04:00
|
|
|
iter::once(Air::PkgStart(S1))
|
2023-01-30 16:51:24 -05:00
|
|
|
.chain(toks.into_iter())
|
2023-03-15 10:59:22 -04:00
|
|
|
.chain(iter::once(Air::PkgEnd(S1))),
|
2023-01-30 16:51:24 -05:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-03-07 13:35:01 -05:00
|
|
|
pub fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
where
|
|
|
|
I::IntoIter: Debug,
|
|
|
|
{
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
assert!(sut.all(|x| x.is_ok()));
|
|
|
|
sut.finalize().unwrap().into_context()
|
|
|
|
}
|