tame/tamer/src/asg/air/opaque.rs

107 lines
3.2 KiB
Rust
Raw Normal View History

// ASG IR opaque object parsing
//
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! AIR opaque object parser.
//!
//! This parser exists primarily to ensure that the parent frame can be held
//! on the stack and considered in lexical scoping operations.
//!
//! See the [parent module](super) for more information.
use super::{super::AsgError, ir::AirIdent, AirAggregate, AirAggregateCtx};
use crate::parse::prelude::*;
#[derive(Debug, PartialEq)]
pub enum AirOpaqueAggregate {
Ready,
}
impl Display for AirOpaqueAggregate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Ready => {
write!(f, "ready for opaque object")
}
}
}
}
impl ParseState for AirOpaqueAggregate {
type Token = AirIdent;
type Object = ();
type Error = AsgError;
type Context = AirAggregateCtx;
type Super = AirAggregate;
fn parse_token(
self,
tok: Self::Token,
ctx: &mut Self::Context,
) -> crate::parse::TransitionResult<Self::Super> {
use super::ir::AirIdent::*;
use AirOpaqueAggregate::*;
match (self, tok) {
(Ready, IdentDecl(name, kind, src)) => ctx
.lookup_lexical_or_missing(name)
.declare(ctx.asg_mut(), name, kind, src)
.map(|_| ())
.transition(Ready),
(Ready, IdentExternDecl(name, kind, src)) => ctx
.lookup_lexical_or_missing(name)
.declare_extern(ctx.asg_mut(), name, kind, src)
.map(|_| ())
.transition(Ready),
(Ready, IdentDep(name, dep)) => {
let oi_from = ctx.lookup_lexical_or_missing(name);
let oi_to = ctx.lookup_lexical_or_missing(dep);
tamer: asg::graph::AsgObjectMut: Allow objects to assert ownership over relationships There's a lot to say about this; it's been a bit of a struggle figuring out what I wanted to do here. First: this allows objects to use `AsgObjectMut` to control whether an edge is permitted to be added, or to cache information about an edge that is about to be added. But no object does that yet; it just uses the default trait implementation, and so this _does not change any current behavior_. It also is approximately equivalent cycle-count-wise, according to Valgrind (within ~100 cycles out of hundreds of millions on large package tests). Adding edges to the graph is still infallible _after having received permission_ from an `ObjectIndexRelTo`, but the object is free to reject the edge with an `AsgError`. As an example of where this will be useful: the template system needs to keep track of what is in the body of a template as it is defined. But the `TplAirAggregate` parser is sidelined while expressions in the body are parsed, and edges are added to a dynamic source using `ObjectIndexRelTo`. Consequently, we cannot rely on a static API to cache information; we have to be able to react dynamically. This will allow `Tpl` objects to know any time edges are added and, therefore, determine their shape as the graph is being built, rather than having to traverse the tree after encountering a close. (I _could_ change this, but `ObjectIndexRelTo` removes a significant amount of complexity for the caller, so I'd rather not.) I did explore other options. I rejected the first one, then rejected this one, then rejected the first one again before returning back to this one after having previously sidelined the entire thing, because of the above example. The core point is: I need confidence that the graph isn't being changed in ways that I forgot about, and because of the complexity of the system and the heavy refactoring that I do, I need the compiler's help; otherwise I risk introducing subtle bugs as objects get out of sync with the actual state of the graph. (I wish the graph supported these things directly, but that's a project well outside the scope of my TAMER work. So I have to make do, as I have been all this time, by layering atop of Petgraph.) (...I'm beginning to ramble.) (...beginning?) Anyway: my other rejected idea was to provide attestation via the `ObjectIndex` APIs to force callers to go through those APIs to add an edge to the graph; it would use sealed objects that are inaccessible to any modules other than the objects, and assert that the caller is able to provide a zero-sized object of that sealed type. The problem with this is...exactly what was mentioned above: `ObjectIndexRelTo` is dynamic. We don't always know the source object type statically, and so we cannot make those static assertions. I could have tried the same tricks to store attestation at some other time, but what a confusing mess it would be. And so here we are. Most of this work is cleaning up the callers---adding edges is now fallible, from the `ObjectIndex` API standpoint, and so AIR needed to be set up to handle those failures. There _aren't_ any failures yet, but again, since things are dynamic, they could appear at any moment. Furthermore, since ref/def is commutative (things can be defined and referenced in any order), there could be surprise errors on edge additions in places that might not otherwise expect it in the future. We're now ready for that, and I'll be able to e.g. traverse incoming edges on a `Missing->Transparent` definition to notify dependents. This project is going to be the end of me. As interesting as it is. I can see why Rust just chose to require macro definitions _before_ use. So much less work. DEV-13163
2023-07-24 16:41:32 -04:00
oi_from
.add_opaque_dep(ctx.asg_mut(), oi_to)
.map(|_| ())
.transition(Ready)
}
(Ready, IdentFragment(name, text)) => ctx
.lookup_lexical_or_missing(name)
.set_fragment(ctx.asg_mut(), text)
.map(|_| ())
.transition(Ready),
tamer: asg::graph::AsgObjectMut: Allow objects to assert ownership over relationships There's a lot to say about this; it's been a bit of a struggle figuring out what I wanted to do here. First: this allows objects to use `AsgObjectMut` to control whether an edge is permitted to be added, or to cache information about an edge that is about to be added. But no object does that yet; it just uses the default trait implementation, and so this _does not change any current behavior_. It also is approximately equivalent cycle-count-wise, according to Valgrind (within ~100 cycles out of hundreds of millions on large package tests). Adding edges to the graph is still infallible _after having received permission_ from an `ObjectIndexRelTo`, but the object is free to reject the edge with an `AsgError`. As an example of where this will be useful: the template system needs to keep track of what is in the body of a template as it is defined. But the `TplAirAggregate` parser is sidelined while expressions in the body are parsed, and edges are added to a dynamic source using `ObjectIndexRelTo`. Consequently, we cannot rely on a static API to cache information; we have to be able to react dynamically. This will allow `Tpl` objects to know any time edges are added and, therefore, determine their shape as the graph is being built, rather than having to traverse the tree after encountering a close. (I _could_ change this, but `ObjectIndexRelTo` removes a significant amount of complexity for the caller, so I'd rather not.) I did explore other options. I rejected the first one, then rejected this one, then rejected the first one again before returning back to this one after having previously sidelined the entire thing, because of the above example. The core point is: I need confidence that the graph isn't being changed in ways that I forgot about, and because of the complexity of the system and the heavy refactoring that I do, I need the compiler's help; otherwise I risk introducing subtle bugs as objects get out of sync with the actual state of the graph. (I wish the graph supported these things directly, but that's a project well outside the scope of my TAMER work. So I have to make do, as I have been all this time, by layering atop of Petgraph.) (...I'm beginning to ramble.) (...beginning?) Anyway: my other rejected idea was to provide attestation via the `ObjectIndex` APIs to force callers to go through those APIs to add an edge to the graph; it would use sealed objects that are inaccessible to any modules other than the objects, and assert that the caller is able to provide a zero-sized object of that sealed type. The problem with this is...exactly what was mentioned above: `ObjectIndexRelTo` is dynamic. We don't always know the source object type statically, and so we cannot make those static assertions. I could have tried the same tricks to store attestation at some other time, but what a confusing mess it would be. And so here we are. Most of this work is cleaning up the callers---adding edges is now fallible, from the `ObjectIndex` API standpoint, and so AIR needed to be set up to handle those failures. There _aren't_ any failures yet, but again, since things are dynamic, they could appear at any moment. Furthermore, since ref/def is commutative (things can be defined and referenced in any order), there could be surprise errors on edge additions in places that might not otherwise expect it in the future. We're now ready for that, and I'll be able to e.g. traverse incoming edges on a `Missing->Transparent` definition to notify dependents. This project is going to be the end of me. As interesting as it is. I can see why Rust just chose to require macro definitions _before_ use. So much less work. DEV-13163
2023-07-24 16:41:32 -04:00
(Ready, IdentRoot(name)) => ctx
.lookup_lexical_or_missing(name)
.root(ctx.asg_mut())
.map(|_| ())
.transition(Ready),
}
}
fn is_accepting(&self, _: &Self::Context) -> bool {
matches!(self, Self::Ready)
}
}
impl AirOpaqueAggregate {
pub(super) fn new() -> Self {
Self::Ready
}
}