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-01-17 22:58:41 -05:00
|
|
|
use super::super::Ident;
|
2022-12-22 16:32:21 -05:00
|
|
|
use super::*;
|
2022-12-13 13:57:04 -05:00
|
|
|
use crate::{
|
tamer: asg::air: AIR as a sum IR
This introduces a new macro `sum_ir!` to help with a long-standing problem
of not being able to easily narrow types in Rust without a whole lot of
boilerplate. This patch includes a bit of documentation, so see that for
more information.
This was not a welcome change---I jumped down this rabbit hole trying to
decompose `AirAggregate` so that I can share portions of parsing with the
current parser and a template parser. I can now proceed with that.
This is not the only implementation that I had tried. I previously inverted
the approach, as I've been doing manually for some time: manually create
types to hold the sets of variants, and then create a sum type to hold those
types. That works, but it resulted in a mess for systems that have to use
the IR, since now you have two enums to contend with. I didn't find that to
be appropriate, because we shouldn't complicate the external API for
implementation details.
The enum for IRs is supposed to be like a bytecode---a list of operations
that can be performed with the IR. They can be grouped if it makes sense
for a public API, but in my case, I only wanted subsets for the sake of
delegating responsibilities to smaller subsystems, while retaining the
context that `match` provides via its exhaustiveness checking but does not
expose as something concrete (which is deeply frustrating!).
Anyway, here we are; this'll be refined over time, hopefully, and
portions of it can be generalized for removing boilerplate from other IRs.
Another thing to note is that this syntax is really a compromise---I had to
move on, and I was spending too much time trying to get creative with
`macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in
some places and is therefore not necessarily all that intuitive. This can
be refined further in the future. But the end result, all things
considered, isn't too bad.
DEV-13708
2023-03-02 15:15:28 -05:00
|
|
|
asg::{
|
|
|
|
graph::object::{expr::ExprRel, ObjectRel},
|
|
|
|
ExprOp, IdentKind, Source,
|
|
|
|
},
|
|
|
|
parse::{ParseError, Parsed, Parser, Token},
|
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-01-30 16:51:24 -05:00
|
|
|
let toks = vec![Air::PkgOpen(S1), Air::PkgClose(S2)];
|
|
|
|
|
|
|
|
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![
|
|
|
|
Air::PkgClose(S1),
|
|
|
|
// RECOVERY: Try again.
|
|
|
|
Air::PkgOpen(S2),
|
|
|
|
Air::PkgClose(S3),
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
|
|
|
Err(ParseError::StateError(AsgError::InvalidPkgCloseContext(S1))),
|
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
|
|
|
],
|
|
|
|
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nested_open_pkg() {
|
|
|
|
let toks = vec![
|
|
|
|
Air::PkgOpen(S1),
|
|
|
|
Air::PkgOpen(S2),
|
|
|
|
// RECOVERY
|
|
|
|
Air::PkgClose(S3),
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
|
|
|
Err(ParseError::StateError(AsgError::NestedPkgOpen(S2, S1))),
|
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
|
|
|
],
|
|
|
|
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse using [`Sut`] when the test does not care about the outer package.
|
|
|
|
fn parse_as_pkg_body<I: IntoIterator<Item = Air>>(
|
|
|
|
toks: I,
|
|
|
|
) -> Parser<Sut, impl Iterator<Item = Air> + Debug>
|
|
|
|
where
|
|
|
|
<I as IntoIterator>::IntoIter: Debug,
|
|
|
|
{
|
|
|
|
use std::iter;
|
|
|
|
|
|
|
|
Sut::parse(
|
|
|
|
iter::once(Air::PkgOpen(S1))
|
|
|
|
.chain(toks.into_iter())
|
|
|
|
.chain(iter::once(Air::PkgClose(S1))),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
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
|
|
|
#[test]
|
2023-01-09 12:02:59 -05:00
|
|
|
fn expr_empty_ident() {
|
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
|
|
|
let id = SPair("foo".into(), S2);
|
|
|
|
|
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S3),
|
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
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
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
|
|
|
assert!(sut.all(|x| x.is_ok()));
|
|
|
|
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
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
|
|
|
|
|
|
|
// The expression should have been bound to this identifier so that
|
|
|
|
// we're able to retrieve it from the graph by name.
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id);
|
|
|
|
assert_eq!(expr.span(), S1.merge(S3).unwrap());
|
|
|
|
}
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
#[test]
|
|
|
|
fn expr_without_pkg() {
|
|
|
|
let toks = vec![
|
|
|
|
// No package
|
|
|
|
// (because we're not parsing with `parse_as_pkg_body` below)
|
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
|
|
|
// RECOVERY
|
|
|
|
Air::PkgOpen(S2),
|
|
|
|
Air::PkgClose(S3),
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-02-28 15:31:49 -05:00
|
|
|
Err(ParseError::StateError(AsgError::PkgExpected(S1))),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
|
|
|
],
|
|
|
|
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note that this can't happen in e.g. NIR / TAME's source XML.
|
|
|
|
#[test]
|
|
|
|
fn close_pkg_mid_expr() {
|
|
|
|
let id = SPair("foo".into(), S4);
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
Air::PkgOpen(S1),
|
|
|
|
Air::ExprOpen(ExprOp::Sum, S2),
|
|
|
|
Air::PkgClose(S3),
|
|
|
|
// RECOVERY: Let's finish the expression first...
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id),
|
2023-01-30 16:51:24 -05:00
|
|
|
Air::ExprClose(S5),
|
|
|
|
// ...and then try to close again.
|
|
|
|
Air::PkgClose(S6),
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
|
|
|
Err(ParseError::StateError(AsgError::InvalidPkgCloseContext(S3))),
|
|
|
|
// RECOVERY: We should be able to close the package if we just
|
|
|
|
// finish the expression first,
|
|
|
|
// demonstrating that recovery properly maintains all state.
|
2023-02-28 11:31:06 -05:00
|
|
|
Ok(Parsed::Incomplete), // BindIdent
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
|
|
|
// Successful close here.
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
|
|
|
],
|
|
|
|
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn open_pkg_mid_expr() {
|
|
|
|
let id = SPair("foo".into(), S4);
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
Air::PkgOpen(S1),
|
|
|
|
Air::ExprOpen(ExprOp::Sum, S2),
|
|
|
|
Air::PkgOpen(S3),
|
|
|
|
// RECOVERY: We should still be able to complete successfully.
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id),
|
2023-01-30 16:51:24 -05:00
|
|
|
Air::ExprClose(S5),
|
|
|
|
// Closes the _original_ package.
|
|
|
|
Air::PkgClose(S6),
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
|
|
|
Err(ParseError::StateError(AsgError::NestedPkgOpen(S3, S1))),
|
|
|
|
// RECOVERY: Ignore the open and continue.
|
|
|
|
// Of course,
|
|
|
|
// this means that any identifiers would be defined in a
|
|
|
|
// different package than was likely intended,
|
|
|
|
// but at least we'll be able to keep processing.
|
2023-02-28 11:31:06 -05:00
|
|
|
Ok(Parsed::Incomplete), // BindIdent
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
|
|
|
],
|
|
|
|
Sut::parse(toks.into_iter()).collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
#[test]
|
2023-01-09 12:02:59 -05:00
|
|
|
fn expr_non_empty_ident_root() {
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
let id_a = SPair("foo".into(), S2);
|
|
|
|
let id_b = SPair("bar".into(), S2);
|
|
|
|
|
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// Identifier while still empty...
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_a),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S3),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// (note that the inner expression _does not_ have an ident binding)
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S4),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// ...and an identifier non-empty.
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_b),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S6),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
];
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
assert!(sut.all(|x| x.is_ok()));
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
let expr_a = asg.expect_ident_obj::<Expr>(id_a);
|
|
|
|
assert_eq!(expr_a.span(), S1.merge(S6).unwrap());
|
|
|
|
|
|
|
|
// Identifiers should reference the same expression.
|
|
|
|
let expr_b = asg.expect_ident_obj::<Expr>(id_b);
|
|
|
|
assert_eq!(expr_a, expr_b);
|
2023-02-03 15:53:50 -05:00
|
|
|
|
|
|
|
// Ontological sanity check:
|
|
|
|
// Child expressions must not be considered cross edges since they are
|
|
|
|
// part of the same tree.
|
|
|
|
let oi_expr_a = asg.expect_ident_oi::<Expr>(id_a);
|
|
|
|
assert!(!oi_expr_a.edges(&asg).any(|rel| rel.is_cross_edge()));
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Binding an identifier after a child expression means that the parser is
|
|
|
|
// creating an expression that is a child of a dangling expression,
|
|
|
|
// which only becomes reachable at the end.
|
|
|
|
#[test]
|
|
|
|
fn expr_non_empty_bind_only_after() {
|
|
|
|
let id = SPair("foo".into(), S2);
|
|
|
|
|
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// Expression root is still dangling at this point.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S2),
|
|
|
|
Air::ExprClose(S3),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// We only bind an identifier _after_ we've created the expression,
|
|
|
|
// which should cause the still-dangling root to become
|
|
|
|
// reachable.
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S5),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
];
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
assert!(sut.all(|x| x.is_ok()));
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id);
|
|
|
|
assert_eq!(expr.span(), S1.merge(S5).unwrap());
|
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
|
|
|
}
|
|
|
|
|
|
|
|
// Danging expressions are unreachable and therefore not useful
|
|
|
|
// constructions.
|
|
|
|
// Prohibit them,
|
|
|
|
// since they're either mistakes or misconceptions.
|
|
|
|
#[test]
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
fn expr_dangling_no_subexpr() {
|
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
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
2023-02-28 11:31:06 -05:00
|
|
|
// No `BindIdent`,
|
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
|
|
|
// so this expression is dangling.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S2),
|
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
|
|
|
];
|
|
|
|
|
|
|
|
// The error span should encompass the entire expression.
|
|
|
|
let full_span = S1.merge(S2).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
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
|
|
|
Ok(Parsed::Incomplete),
|
2023-01-30 16:51:24 -05:00
|
|
|
Err(ParseError::StateError(AsgError::DanglingExpr(full_span))),
|
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
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
|
|
|
parse_as_pkg_body(toks).collect::<Vec<_>>(),
|
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
|
|
|
);
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expr_dangling_with_subexpr() {
|
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// Expression root is still dangling at this point.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S2),
|
|
|
|
Air::ExprClose(S3),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// Still no ident binding,
|
|
|
|
// so root should still be dangling.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S4),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
let full_span = S1.merge(S4).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
2023-01-30 16:51:24 -05:00
|
|
|
Err(ParseError::StateError(AsgError::DanglingExpr(full_span))),
|
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
],
|
2023-01-30 16:51:24 -05:00
|
|
|
parse_as_pkg_body(toks).collect::<Vec<_>>(),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expr_dangling_with_subexpr_ident() {
|
|
|
|
let id = SPair("foo".into(), S3);
|
|
|
|
|
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// Expression root is still dangling at this point.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S2),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// The _inner_ expression receives an identifier,
|
|
|
|
// but that should have no impact on the dangling status of the
|
|
|
|
// root,
|
|
|
|
// especially given that subexpressions are always reachable
|
|
|
|
// anyway.
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S4),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// But the root still has no ident binding,
|
|
|
|
// and so should still be dangling.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S5),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
let full_span = S1.merge(S5).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
2023-01-30 16:51:24 -05:00
|
|
|
Err(ParseError::StateError(AsgError::DanglingExpr(full_span))),
|
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
],
|
2023-01-30 16:51:24 -05:00
|
|
|
parse_as_pkg_body(toks).collect::<Vec<_>>(),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that the parser correctly recognizes dangling expressions after
|
|
|
|
// having encountered a reachable expression.
|
|
|
|
// Ideally the parser will have been written to make this impossible,
|
|
|
|
// but this also protects against potential future breakages.
|
|
|
|
#[test]
|
|
|
|
fn expr_reachable_subsequent_dangling() {
|
|
|
|
let id = SPair("foo".into(), S2);
|
|
|
|
let toks = vec![
|
|
|
|
// Reachable
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S3),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// Dangling
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S4),
|
|
|
|
Air::ExprClose(S5),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
// The error span should encompass the entire expression.
|
|
|
|
// TODO: ...let's actually have something inside this expression.
|
|
|
|
let second_span = S4.merge(S5).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
2023-01-30 16:51:24 -05:00
|
|
|
Err(ParseError::StateError(AsgError::DanglingExpr(second_span))),
|
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
],
|
2023-01-30 16:51:24 -05:00
|
|
|
parse_as_pkg_body(toks).collect::<Vec<_>>(),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recovery from dangling expression.
|
|
|
|
#[test]
|
|
|
|
fn recovery_expr_reachable_after_dangling() {
|
|
|
|
let id = SPair("foo".into(), S4);
|
|
|
|
let toks = vec![
|
|
|
|
// Dangling
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
|
|
|
Air::ExprClose(S2),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// Reachable, after error from dangling.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S3),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S5),
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
// The error span should encompass the entire expression.
|
|
|
|
let err_span = S1.merge(S2).unwrap();
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Err(ParseError::StateError(AsgError::DanglingExpr(err_span))),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY: continue at this point with the next expression.
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
|
|
|
Ok(Parsed::Incomplete),
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
],
|
|
|
|
sut.by_ref().collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
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
|
|
|
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
// Let's make sure that we _actually_ added it to the graph,
|
|
|
|
// despite the previous error.
|
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id);
|
|
|
|
assert_eq!(expr.span(), S3.merge(S5).unwrap());
|
|
|
|
|
|
|
|
// The dangling expression may or may not be on the graph,
|
|
|
|
// but it doesn't matter;
|
|
|
|
// we cannot reference it
|
|
|
|
// (unless we break abstraction and walk the underlying graph).
|
|
|
|
// Let's leave this undefined so that we have flexibility in what we
|
|
|
|
// decide to do in the future.
|
|
|
|
// So we end here.
|
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-09 12:02:59 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expr_close_unbalanced() {
|
|
|
|
let id = SPair("foo".into(), S3);
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
// Close before _any_ open.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S1),
|
2023-01-09 12:02:59 -05:00
|
|
|
// Should recover,
|
|
|
|
// allowing for a normal expr.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S2),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S4),
|
2023-01-09 12:02:59 -05:00
|
|
|
// And now an extra close _after_ a valid expr.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S5),
|
2023-01-09 12:02:59 -05:00
|
|
|
];
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
2023-01-09 12:02:59 -05:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
2023-01-09 12:02:59 -05:00
|
|
|
Err(ParseError::StateError(AsgError::UnbalancedExpr(S1))),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
2023-02-28 11:31:06 -05:00
|
|
|
Ok(Parsed::Incomplete), // BindIdent
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
2023-01-09 12:02:59 -05:00
|
|
|
// Another error after a successful expression.
|
|
|
|
Err(ParseError::StateError(AsgError::UnbalancedExpr(S5))),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
2023-01-09 12:02:59 -05:00
|
|
|
],
|
|
|
|
sut.by_ref().collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
// Just verify that the expression was successfully added after recovery.
|
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id);
|
|
|
|
assert_eq!(expr.span(), S2.merge(S4).unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expr_bind_to_empty() {
|
|
|
|
let id_noexpr_a = SPair("noexpr_a".into(), S1);
|
|
|
|
let id_good = SPair("noexpr".into(), S3);
|
|
|
|
let id_noexpr_b = SPair("noexpr_b".into(), S5);
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
// No open expression to bind to.
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_noexpr_a),
|
2023-01-09 12:02:59 -05:00
|
|
|
// Post-recovery create an expression.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S2),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_good),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S4),
|
2023-01-09 12:02:59 -05:00
|
|
|
// Once again we have nothing to bind to.
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_noexpr_b),
|
2023-01-09 12:02:59 -05:00
|
|
|
];
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
2023-01-09 12:02:59 -05:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
2023-01-09 12:02:59 -05:00
|
|
|
Err(ParseError::StateError(AsgError::InvalidExprBindContext(
|
|
|
|
id_noexpr_a
|
|
|
|
))),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
2023-02-28 11:31:06 -05:00
|
|
|
Ok(Parsed::Incomplete), // BindIdent
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
2023-01-09 12:02:59 -05:00
|
|
|
// Another error after a successful expression.
|
|
|
|
Err(ParseError::StateError(AsgError::InvalidExprBindContext(
|
|
|
|
id_noexpr_b
|
|
|
|
))),
|
2023-01-30 16:51:24 -05:00
|
|
|
// RECOVERY
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
2023-01-09 12:02:59 -05:00
|
|
|
],
|
|
|
|
sut.by_ref().collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
// Neither of the identifiers outside of expressions should exist on the
|
|
|
|
// graph.
|
|
|
|
assert_eq!(None, asg.get_ident_obj::<Expr>(id_noexpr_a));
|
|
|
|
assert_eq!(None, asg.get_ident_obj::<Expr>(id_noexpr_b));
|
|
|
|
|
|
|
|
// Verify that the expression was successfully added after recovery.
|
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id_good);
|
|
|
|
assert_eq!(expr.span(), S2.merge(S4).unwrap());
|
|
|
|
}
|
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
|
|
|
|
|
|
|
// Subexpressions should not only have edges to their parent,
|
|
|
|
// but those edges ought to be ordered,
|
|
|
|
// allowing TAME to handle non-commutative expressions.
|
|
|
|
// We must further understand the relative order in which edges are stored
|
|
|
|
// for non-associative expressions.
|
|
|
|
#[test]
|
|
|
|
fn sibling_subexprs_have_ordered_edges_to_parent() {
|
|
|
|
let id_root = SPair("root".into(), S1);
|
|
|
|
|
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
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
|
|
|
// Identify the root so that it is not dangling.
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_root),
|
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
|
|
|
// Sibling A
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S3),
|
|
|
|
Air::ExprClose(S4),
|
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
|
|
|
// Sibling B
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S5),
|
|
|
|
Air::ExprClose(S6),
|
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
|
|
|
// Sibling C
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S7),
|
|
|
|
Air::ExprClose(S8),
|
|
|
|
Air::ExprClose(S9),
|
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
|
|
|
];
|
|
|
|
|
|
|
|
let asg = asg_from_toks(toks);
|
|
|
|
|
|
|
|
// The root is the parent expression that should contain edges to each
|
|
|
|
// subexpression
|
|
|
|
// (the siblings above).
|
|
|
|
// Note that we retrieve its _index_,
|
|
|
|
// not the object itself.
|
|
|
|
let oi_root = asg.expect_ident_oi::<Expr>(id_root);
|
|
|
|
|
|
|
|
let siblings = oi_root
|
2023-01-31 22:00:51 -05:00
|
|
|
.edges_filtered::<Expr>(&asg)
|
2023-01-23 11:40:10 -05:00
|
|
|
.map(ObjectIndex::cresolve(&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
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
// The reversal here is an implementation detail with regards to how
|
|
|
|
// Petgraph stores its edges as effectively linked lists,
|
|
|
|
// using node indices instead of pointers.
|
|
|
|
// It is very important that we understand this behavior.
|
|
|
|
assert_eq!(siblings.len(), 3);
|
|
|
|
assert_eq!(siblings[2].span(), S3.merge(S4).unwrap());
|
|
|
|
assert_eq!(siblings[1].span(), S5.merge(S6).unwrap());
|
|
|
|
assert_eq!(siblings[0].span(), S7.merge(S8).unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nested_subexprs_related_to_relative_parent() {
|
|
|
|
let id_root = SPair("root".into(), S1);
|
|
|
|
let id_suba = SPair("suba".into(), S2);
|
|
|
|
|
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1), // 0
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_root),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S2), // 1
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_suba),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S3), // 2
|
|
|
|
Air::ExprClose(S4),
|
|
|
|
Air::ExprClose(S5),
|
|
|
|
Air::ExprClose(S6),
|
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
|
|
|
];
|
|
|
|
|
|
|
|
let asg = asg_from_toks(toks);
|
|
|
|
|
|
|
|
let oi_0 = asg.expect_ident_oi::<Expr>(id_root);
|
|
|
|
let subexprs_0 = collect_subexprs(&asg, oi_0);
|
|
|
|
|
|
|
|
// Subexpr 1
|
|
|
|
assert_eq!(subexprs_0.len(), 1);
|
|
|
|
let (oi_1, subexpr_1) = subexprs_0[0];
|
|
|
|
assert_eq!(subexpr_1.span(), S2.merge(S5).unwrap());
|
|
|
|
|
|
|
|
let subexprs_1 = collect_subexprs(&asg, oi_1);
|
|
|
|
|
|
|
|
// Subexpr 2
|
|
|
|
assert_eq!(subexprs_1.len(), 1);
|
|
|
|
let (_, subexpr_2) = subexprs_1[0];
|
|
|
|
assert_eq!(subexpr_2.span(), S3.merge(S4).unwrap());
|
|
|
|
}
|
|
|
|
|
2023-01-17 16:31:13 -05:00
|
|
|
#[test]
|
|
|
|
fn expr_redefine_ident() {
|
|
|
|
// Same identifier but with different spans
|
|
|
|
// (which would be the case in the real world).
|
|
|
|
let id_first = SPair("foo".into(), S2);
|
|
|
|
let id_dup = SPair("foo".into(), S3);
|
|
|
|
|
|
|
|
let toks = vec![
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_first),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S3),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_dup),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S4),
|
|
|
|
Air::ExprClose(S5),
|
2023-01-17 16:31:13 -05:00
|
|
|
];
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
2023-01-17 16:31:13 -05:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
2023-02-28 11:31:06 -05:00
|
|
|
Ok(Parsed::Incomplete), // BindIdent (first)
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
2023-01-17 16:31:13 -05:00
|
|
|
Err(ParseError::StateError(AsgError::IdentRedefine(
|
|
|
|
id_first,
|
|
|
|
id_dup.span(),
|
|
|
|
))),
|
|
|
|
// RECOVERY: Ignore the attempt to redefine and continue.
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
2023-01-17 16:31:13 -05:00
|
|
|
],
|
|
|
|
sut.by_ref().collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
// The identifier should continue to reference the first expression.
|
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id_first);
|
|
|
|
assert_eq!(expr.span(), S1.merge(S5).unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Similar to the above test,
|
|
|
|
// but with two entirely separate expressions,
|
|
|
|
// such that a failure to identify an expression ought to leave it in an
|
|
|
|
// unreachable state.
|
|
|
|
#[test]
|
|
|
|
fn expr_still_dangling_on_redefine() {
|
|
|
|
// Same identifier but with different spans
|
|
|
|
// (which would be the case in the real world).
|
|
|
|
let id_first = SPair("foo".into(), S2);
|
|
|
|
let id_dup = SPair("foo".into(), S5);
|
|
|
|
let id_dup2 = SPair("foo".into(), S8);
|
|
|
|
let id_second = SPair("bar".into(), S9);
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
// First expr (OK)
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_first),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S3),
|
2023-01-17 16:31:13 -05:00
|
|
|
// Second expr should still dangle due to use of duplicate
|
|
|
|
// identifier
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S4),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_dup),
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S6),
|
2023-01-17 16:31:13 -05:00
|
|
|
// Third expr will error on redefine but then be successful.
|
|
|
|
// This probably won't happen in practice with TAME's original
|
|
|
|
// source language,
|
|
|
|
// but could happen at e.g. a REPL.
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S7),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_dup2), // fail
|
|
|
|
Air::BindIdent(id_second), // succeed
|
2023-01-19 16:24:21 -05:00
|
|
|
Air::ExprClose(S10),
|
2023-01-17 16:31:13 -05:00
|
|
|
];
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
2023-01-17 16:31:13 -05:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
2023-02-28 11:31:06 -05:00
|
|
|
Ok(Parsed::Incomplete), // BindIdent (first)
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
2023-01-17 16:31:13 -05:00
|
|
|
// Beginning of second expression
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
2023-01-17 16:31:13 -05:00
|
|
|
Err(ParseError::StateError(AsgError::IdentRedefine(
|
|
|
|
id_first,
|
|
|
|
id_dup.span(),
|
|
|
|
))),
|
|
|
|
// RECOVERY: Ignore the attempt to redefine and continue.
|
|
|
|
// ...but then immediately fail _again_ because we've closed a
|
|
|
|
// dangling expression.
|
|
|
|
Err(ParseError::StateError(AsgError::DanglingExpr(
|
|
|
|
S4.merge(S6).unwrap()
|
|
|
|
))),
|
|
|
|
// RECOVERY: But we'll continue onto one final expression,
|
|
|
|
// which we will fail to define but then subsequently define
|
|
|
|
// successfully.
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
2023-01-17 16:31:13 -05:00
|
|
|
Err(ParseError::StateError(AsgError::IdentRedefine(
|
|
|
|
id_first,
|
|
|
|
id_dup2.span(),
|
|
|
|
))),
|
|
|
|
// RECOVERY: Despite the initial failure,
|
|
|
|
// we can now re-attempt to bind with a unique id.
|
2023-02-28 11:31:06 -05:00
|
|
|
Ok(Parsed::Incomplete), // BindIdent (second)
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
2023-01-17 16:31:13 -05:00
|
|
|
],
|
|
|
|
sut.by_ref().collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
// The identifier should continue to reference the first expression.
|
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id_first);
|
|
|
|
assert_eq!(expr.span(), S1.merge(S3).unwrap());
|
|
|
|
|
|
|
|
// There's nothing we can do using the ASG's public API at the time of
|
|
|
|
// writing to try to reference the dangling expression.
|
|
|
|
|
|
|
|
// The second identifier should have been successfully bound despite the
|
|
|
|
// failed initial attempt.
|
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id_second);
|
|
|
|
assert_eq!(expr.span(), S7.merge(S10).unwrap());
|
|
|
|
}
|
|
|
|
|
2023-01-23 15:49:25 -05:00
|
|
|
#[test]
|
|
|
|
fn expr_ref_to_ident() {
|
|
|
|
let id_foo = SPair("foo".into(), S2);
|
|
|
|
let id_bar = SPair("bar".into(), S6);
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_foo),
|
2023-02-07 10:23:48 -05:00
|
|
|
// Reference to an as-of-yet-undefined id (okay),
|
|
|
|
// with a different span than `id_bar`.
|
|
|
|
Air::ExprRef(SPair("bar".into(), S3)),
|
2023-01-23 15:49:25 -05:00
|
|
|
Air::ExprClose(S4),
|
|
|
|
//
|
|
|
|
// Another expression to reference the first
|
|
|
|
// (we don't handle cyclic references until a topological sort,
|
|
|
|
// so no point in referencing ourselves;
|
|
|
|
// it'd work just fine here.)
|
|
|
|
Air::ExprOpen(ExprOp::Sum, S5),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_bar),
|
2023-01-23 15:49:25 -05:00
|
|
|
Air::ExprClose(S7),
|
|
|
|
];
|
|
|
|
|
|
|
|
let asg = asg_from_toks(toks);
|
|
|
|
|
|
|
|
let oi_foo = asg.expect_ident_oi::<Expr>(id_foo);
|
|
|
|
|
2023-02-03 15:53:50 -05:00
|
|
|
let mut foo_rels = oi_foo
|
|
|
|
.edges(&asg)
|
|
|
|
.filter_map(ExprRel::narrows_into::<Ident>)
|
|
|
|
.collect::<Vec<_>>();
|
2023-01-23 15:49:25 -05:00
|
|
|
|
|
|
|
// We should have only a single reference (to `id_bar`).
|
2023-02-03 15:53:50 -05:00
|
|
|
assert_eq!(foo_rels.len(), 1);
|
2023-01-23 15:49:25 -05:00
|
|
|
|
2023-02-03 15:53:50 -05:00
|
|
|
// Ontological sanity check:
|
|
|
|
// references to identifiers should count as cross edges.
|
|
|
|
// This is very important to ensure that certain graph traversals work
|
|
|
|
// correctly between trees.
|
|
|
|
assert!(foo_rels.iter().all(|rel| rel.is_cross_edge()));
|
|
|
|
|
|
|
|
let oi_ident_bar =
|
|
|
|
foo_rels.pop().and_then(ExprRel::narrow::<Ident>).unwrap();
|
2023-01-23 15:49:25 -05:00
|
|
|
let ident_bar = oi_ident_bar.resolve(&asg);
|
|
|
|
|
|
|
|
// The identifier will have originally been `Missing`,
|
|
|
|
// since it did not exist at the point of reference.
|
|
|
|
// But it should now properly identify the other expression.
|
|
|
|
assert_matches!(ident_bar, Ident::Transparent(..));
|
|
|
|
|
2023-02-07 10:23:48 -05:00
|
|
|
// The span of the identifier must be updated with the defining
|
2023-02-28 11:31:06 -05:00
|
|
|
// `BindIdent`,
|
2023-02-07 10:23:48 -05:00
|
|
|
// otherwise it'll be the location of the `ExprRef` that originally
|
|
|
|
// added it as `Missing`.
|
|
|
|
assert_eq!(ident_bar.span(), id_bar.span());
|
|
|
|
|
2023-01-23 15:49:25 -05:00
|
|
|
let oi_expr_bar = asg.expect_ident_oi::<Expr>(id_bar);
|
|
|
|
assert!(oi_ident_bar.is_bound_to(&asg, oi_expr_bar));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expr_ref_outside_of_expr_context() {
|
|
|
|
let id_foo = SPair("foo".into(), S2);
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
// This will fail since we're not in an expression context.
|
|
|
|
Air::ExprRef(id_foo),
|
|
|
|
// RECOVERY: Simply ignore the above.
|
|
|
|
Air::ExprOpen(ExprOp::Sum, S1),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_foo),
|
2023-01-23 15:49:25 -05:00
|
|
|
Air::ExprClose(S3),
|
|
|
|
];
|
|
|
|
|
2023-01-30 16:51:24 -05:00
|
|
|
let mut sut = parse_as_pkg_body(toks);
|
2023-01-23 15:49:25 -05:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
vec![
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // PkgOpen
|
2023-01-23 15:49:25 -05:00
|
|
|
Err(ParseError::StateError(AsgError::InvalidExprRefContext(
|
|
|
|
id_foo
|
|
|
|
))),
|
|
|
|
// RECOVERY: Proceed as normal
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprOpen
|
2023-02-28 11:31:06 -05:00
|
|
|
Ok(Parsed::Incomplete), // BindIdent
|
2023-01-30 16:51:24 -05:00
|
|
|
Ok(Parsed::Incomplete), // ExprClose
|
|
|
|
Ok(Parsed::Incomplete), // PkgClose
|
2023-01-23 15:49:25 -05:00
|
|
|
],
|
|
|
|
sut.by_ref().collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
// Verify that the identifier was bound just to have some confidence in
|
|
|
|
// the recovery.
|
|
|
|
let expr = asg.expect_ident_obj::<Expr>(id_foo);
|
|
|
|
assert_eq!(expr.span(), S1.merge(S3).unwrap());
|
|
|
|
}
|
|
|
|
|
2023-01-31 16:37:25 -05:00
|
|
|
#[test]
|
|
|
|
fn idents_share_defining_pkg() {
|
2023-02-07 12:19:27 -05:00
|
|
|
let id_foo = SPair("foo".into(), S3);
|
|
|
|
let id_bar = SPair("bar".into(), S5);
|
|
|
|
let id_baz = SPair("baz".into(), S6);
|
2023-01-31 16:37:25 -05:00
|
|
|
|
|
|
|
// An expression nested within another.
|
|
|
|
let toks = vec![
|
2023-02-07 12:19:27 -05:00
|
|
|
Air::PkgOpen(S1),
|
|
|
|
Air::ExprOpen(ExprOp::Sum, S2),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_foo),
|
2023-02-07 12:19:27 -05:00
|
|
|
Air::ExprOpen(ExprOp::Sum, S4),
|
2023-02-28 11:31:06 -05:00
|
|
|
Air::BindIdent(id_bar),
|
2023-01-31 16:37:25 -05:00
|
|
|
Air::ExprRef(id_baz),
|
|
|
|
Air::ExprClose(S7),
|
2023-02-07 12:19:27 -05:00
|
|
|
Air::ExprClose(S8),
|
|
|
|
Air::PkgClose(S9),
|
2023-01-31 16:37:25 -05:00
|
|
|
];
|
|
|
|
|
2023-02-07 12:19:27 -05:00
|
|
|
let mut sut = Sut::parse(toks.into_iter());
|
|
|
|
assert!(sut.all(|x| x.is_ok()));
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
2023-01-31 16:37:25 -05:00
|
|
|
|
|
|
|
let oi_foo = asg.lookup(id_foo).unwrap();
|
|
|
|
let oi_bar = asg.lookup(id_bar).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(oi_foo.src_pkg(&asg).unwrap(), oi_bar.src_pkg(&asg).unwrap());
|
|
|
|
|
2023-02-07 12:19:27 -05:00
|
|
|
// Missing identifiers should not have a source package,
|
|
|
|
// since we don't know what defined it yet.
|
|
|
|
let oi_baz = asg.lookup(id_baz).unwrap();
|
|
|
|
assert_eq!(None, oi_baz.src_pkg(&asg));
|
|
|
|
|
2023-02-03 15:53:50 -05:00
|
|
|
// Ontological sanity check:
|
|
|
|
// edges from the package to identifiers defined by it should not be
|
|
|
|
// considered cross edges.
|
|
|
|
let oi_pkg = oi_foo.src_pkg(&asg).unwrap();
|
|
|
|
assert!(oi_pkg.edges(&asg).all(|rel| !rel.is_cross_edge()));
|
|
|
|
|
2023-02-07 12:19:27 -05:00
|
|
|
// The package span should encompass the entire definition.
|
|
|
|
assert_eq!(
|
|
|
|
S1.merge(S9),
|
|
|
|
oi_foo.src_pkg(&asg).map(|pkg| pkg.resolve(&asg).span())
|
|
|
|
)
|
2023-01-31 16:37:25 -05:00
|
|
|
}
|
|
|
|
|
2023-02-28 15:31:49 -05:00
|
|
|
// A template is defined by the package containing it,
|
|
|
|
// like an expression.
|
|
|
|
#[test]
|
|
|
|
fn tpl_defining_pkg() {
|
|
|
|
let id_tpl = SPair("_tpl_".into(), S3);
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
Air::PkgOpen(S1),
|
|
|
|
Air::TplOpen(S2),
|
|
|
|
Air::BindIdent(id_tpl),
|
|
|
|
Air::TplClose(S4),
|
|
|
|
Air::PkgClose(S5),
|
|
|
|
];
|
|
|
|
|
|
|
|
let mut sut = Sut::parse(toks.into_iter());
|
|
|
|
assert!(sut.all(|x| x.is_ok()));
|
|
|
|
let asg = sut.finalize().unwrap().into_context();
|
|
|
|
|
|
|
|
let tpl = asg.expect_ident_obj::<Tpl>(id_tpl);
|
|
|
|
assert_eq!(S2.merge(S4).unwrap(), tpl.span());
|
|
|
|
|
|
|
|
let oi_id_tpl = asg.lookup(id_tpl).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
S1.merge(S5),
|
|
|
|
oi_id_tpl.src_pkg(&asg).map(|pkg| pkg.resolve(&asg).span()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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
|
|
|
fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2023-01-23 11:40:10 -05:00
|
|
|
fn collect_subexprs(
|
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
|
|
|
asg: &Asg,
|
2023-01-23 11:40:10 -05:00
|
|
|
oi: ObjectIndex<Expr>,
|
|
|
|
) -> Vec<(ObjectIndex<Expr>, &Expr)> {
|
|
|
|
oi.edges(&asg)
|
|
|
|
.filter_map(|rel| rel.narrow::<Expr>())
|
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
|
|
|
.map(|oi| (oi, oi.resolve(&asg)))
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
}
|