tamer: src::asg::air: Pool identifiers into global environment

This, finally, introduces identifier pooling in the global environment,
represented by `Root`.  All package-level identifiers will be scoped as
such, which at the moment means anything that's not within a template.

As mentioned in recent commits, this does require additional cleanup to
finalize, and some more test will make additional rationale more clear.

It's also worth noting the intent of storing the `ObjectIndex<Root>`---not
only does it mean that the active root can be derived solely from the
current parsing state, but it also means that in the future we can
contribute to any, potentially multiple, roots.  I had previously used Neo4J
to effectively diff two dependency graphs between versions in the current
XSLT-based TAMER; I'd like to be able to do that with TAMER in the future,
which is an important concept when considering automated data migration, as
well as querying for the effects of changes.

More to come.  I'm hoping this is finally nearing a conclusion and I can
finally tie everything together with package imports.  `AirIdent` will be
introduced into the mix soon now too, now that this commit is able to root
them.

DEV-13162
main
Mike Gerwitz 2023-05-16 23:16:38 -04:00
parent 1cf5488756
commit ba38a3c1ba
3 changed files with 99 additions and 44 deletions

View File

@ -36,7 +36,7 @@
//! air such cringeworthy dad jokes here.
use super::{
graph::object::{Object, ObjectIndexTo, ObjectIndexToTree, Pkg, Tpl},
graph::object::{Object, ObjectIndexTo, ObjectIndexToTree, Pkg, Root, Tpl},
Asg, AsgError, Expr, Ident, ObjectIndex,
};
use crate::{
@ -64,13 +64,16 @@ pub type DepSym = SymbolId;
/// AIR parser state.
#[derive(Debug, PartialEq, Default)]
pub enum AirAggregate {
/// Parser has not yet been initialized.
#[default]
Uninit,
/// Parser is in the root context.
///
/// As a parser,
/// this does nothing but await work.
/// Its presence in the [`AirStack`] is used for the global environment.
#[default]
Root,
Root(ObjectIndex<Root>),
/// Parsing a package.
Pkg(AirPkgAggregate),
@ -95,7 +98,8 @@ impl Display for AirAggregate {
use AirAggregate::*;
match self {
Root => write!(f, "awaiting AIR input for ASG"),
Uninit => write!(f, "awaiting AIR input"),
Root(_) => write!(f, "awaiting input at root"),
Pkg(pkg) => {
write!(f, "defining a package: {pkg}")
}
@ -147,10 +151,17 @@ impl ParseState for AirAggregate {
use AirAggregate::*;
match (self, tok.into()) {
// Initialize the parser with the graph root.
// The graph may contain multiple roots in the future to support
// cross-version analysis.
(Uninit, tok) => Transition(Root(ctx.asg_mut().root(tok.span())))
.incomplete()
.with_lookahead(tok),
(st, AirTodo(Todo(_))) => Transition(st).incomplete(),
// Package
(st @ (Root | PkgExpr(..) | PkgTpl(..)), tok @ AirPkg(..)) => {
(st @ (Root(..) | PkgExpr(..) | PkgTpl(..)), tok @ AirPkg(..)) => {
ctx.ret_or_transfer(st, tok, AirPkgAggregate::new())
}
(Pkg(pkg), AirPkg(etok)) => ctx.proxy(pkg, etok),
@ -175,11 +186,11 @@ impl ParseState for AirAggregate {
(PkgTpl(tplst), AirDoc(ttok)) => ctx.proxy(tplst, ttok),
(
Root,
st @ Root(_),
tok @ (AirExpr(..) | AirBind(..) | AirTpl(..) | AirDoc(..)),
) => Transition(Root).err(AsgError::PkgExpected(tok.span())),
) => Transition(st).err(AsgError::PkgExpected(tok.span())),
(st @ (Root | PkgExpr(..) | PkgTpl(..)), AirIdent(tok)) => {
(st @ (Root(..) | PkgExpr(..) | PkgTpl(..)), AirIdent(tok)) => {
Transition(st).err(AsgError::UnexpectedOpaqueIdent(tok.name()))
}
}
@ -203,9 +214,11 @@ impl AirAggregate {
use AirAggregate::*;
match self {
Uninit => false,
// We can't be done with something we're not doing.
// This is necessary to start the first child parser.
Root => false,
Root(_) => false,
Pkg(st) => st.is_accepting(ctx),
PkgExpr(st) => st.is_accepting(ctx),
@ -218,9 +231,11 @@ impl AirAggregate {
use AirAggregate::*;
match self {
Uninit => false,
// This must not recurse on `AirAggregate::is_accepting`,
// otherwise it'll be mutually recursive.
Root => true,
Root(_) => true,
Pkg(st) => st.is_accepting(ctx),
PkgExpr(st) => st.is_accepting(ctx),
@ -235,27 +250,48 @@ impl AirAggregate {
/// but a parent context may
/// (see [`AirAggregateCtx::rooting_oi`]).
fn active_rooting_oi(&self) -> Option<ObjectIndexToTree<Ident>> {
use AirAggregate::*;
match self {
AirAggregate::Root => None,
Uninit => None,
// Root will serve as a pool of identifiers,
// but it can never _contain_ their definitions.
// See `active_env_oi`.
Root(_) => None,
// Packages always serve as roots for identifiers
// (that is their entire purpose).
AirAggregate::Pkg(pkgst) => pkgst.active_pkg_oi().map(Into::into),
Pkg(pkgst) => pkgst.active_pkg_oi().map(Into::into),
// Expressions never serve as roots for identifiers;
// this will always fall through to the parent context.
// Since the parent context is a package or a template,
// the next frame should succeed.
AirAggregate::PkgExpr(_) => None,
PkgExpr(_) => None,
// Identifiers bound while within a template definition context
// must bind to the eventual _expansion_ site,
// as if the body were pasted there.
// Templates must therefore serve as containers for identifiers
// bound therein.
AirAggregate::PkgTpl(tplst) => {
tplst.active_tpl_oi().map(Into::into)
}
PkgTpl(tplst) => tplst.active_tpl_oi().map(Into::into),
}
}
/// Active environment for identifier lookups.
///
/// An environment is a superset of a container,
/// which is described by [`Self::active_rooting_oi`].
/// For example,
/// [`Self::Root`] cannot own any identifiers,
/// but it can serve as a pool of references to them.
fn active_env_oi(&self) -> Option<ObjectIndexTo<Ident>> {
use AirAggregate::*;
match self {
Root(oi_root) => Some((*oi_root).into()),
_ => self.active_rooting_oi().map(Into::into),
}
}
@ -276,6 +312,9 @@ impl AirAggregate {
use EnvScopeKind::*;
match (self, kind) {
// This is not an environment.
(Uninit, kind) => kind,
// Hidden is a fixpoint.
(_, kind @ Hidden(_)) => kind,
@ -293,12 +332,12 @@ impl AirAggregate {
// Consequently,
// Visible at Root means that we're a package-level Visible,
// which must contribute to the pool.
(Root, Visible(x)) => Visible(x),
(Root(_), Visible(x)) => Visible(x),
// If we're _not_ Visible at the root,
// then we're _not_ a package-level definition,
// and so we should _not_ contribute to the pool.
(Root, Shadow(x)) => Hidden(x),
(Root(_), Shadow(x)) => Hidden(x),
}
}
}
@ -394,8 +433,8 @@ impl AirAggregateCtx {
let st_super = st.into();
if st_super.active_is_complete(self) {
// TODO: dead state or error
self.stack().ret_or_dead(AirAggregate::Root, tok)
// TODO: error (this should never happen, so maybe panic instead?)
self.stack().ret_or_dead(AirAggregate::Uninit, tok)
} else {
self.stack().transfer_with_ret(
Transition(st_super),
@ -415,7 +454,8 @@ impl AirAggregateCtx {
tok: impl Token + Into<S::Token>,
) -> TransitionResult<AirAggregate> {
st.delegate_child(tok.into(), self, |_deadst, tok, ctx| {
ctx.stack().ret_or_dead(AirAggregate::Root, tok)
// TODO: error (this should never happen, so maybe panic instead?)
ctx.stack().ret_or_dead(AirAggregate::Uninit, tok)
})
}
@ -467,30 +507,33 @@ impl AirAggregateCtx {
/// dangle in the current context
/// (and so must be identified).
fn dangling_expr_oi(&self) -> Option<ObjectIndexTo<Expr>> {
use AirAggregate::*;
let Self(_, stack, _) = self;
stack.iter().rev().find_map(|st| match st {
AirAggregate::Root => None,
Uninit => None,
// It should never be possible to define expressions directly in
// Root.
Root(_) => None,
// A dangling expression in a package context would be
// unreachable.
// There should be no parent frame and so this will fail to find
// a value.
AirAggregate::Pkg(_) => None,
Pkg(_) => None,
// Expressions may always contain other expressions,
// and so this method should not be consulted in such a
// context.
// Nonetheless,
// fall through to the parent frame and give a correct answer.
AirAggregate::PkgExpr(_) => None,
PkgExpr(_) => None,
// Templates serve as containers for dangling expressions,
// since they may expand into an context where they are not
// considered to be dangling.
AirAggregate::PkgTpl(tplst) => {
tplst.active_tpl_oi().map(Into::into)
}
PkgTpl(tplst) => tplst.active_tpl_oi().map(Into::into),
})
}
@ -499,17 +542,15 @@ impl AirAggregateCtx {
/// A value of [`None`] indicates that template expansion is not
/// permitted in this current context.
fn expansion_oi(&self) -> Option<ObjectIndexTo<Tpl>> {
use AirAggregate::*;
let Self(_, stack, _) = self;
stack.iter().rev().find_map(|st| match st {
AirAggregate::Root => None,
AirAggregate::Pkg(pkg_st) => pkg_st.active_pkg_oi().map(Into::into),
AirAggregate::PkgExpr(exprst) => {
exprst.active_expr_oi().map(Into::into)
}
AirAggregate::PkgTpl(tplst) => {
tplst.active_tpl_oi().map(Into::into)
}
Uninit => None,
Root(_) => None,
Pkg(pkg_st) => pkg_st.active_pkg_oi().map(Into::into),
PkgExpr(exprst) => exprst.active_expr_oi().map(Into::into),
PkgTpl(tplst) => tplst.active_tpl_oi().map(Into::into),
})
}
@ -546,7 +587,7 @@ impl AirAggregateCtx {
stack
.iter()
.filter_map(|st| st.active_rooting_oi())
.filter_map(|st| st.active_env_oi())
.find_map(|oi| asg.lookup(oi, name))
.unwrap_or_else(|| self.create_env_indexed_ident(name))
}
@ -562,13 +603,20 @@ impl AirAggregateCtx {
stack
.iter()
.rev()
.filter_map(|frame| frame.active_rooting_oi().map(|oi| (oi, frame)))
.filter_map(|frame| frame.active_env_oi().map(|oi| (oi, frame)))
.fold(None, |oeoi, (imm_oi, frame)| {
let eoi_next = oeoi
.map(|eoi| frame.env_cross_boundary_into(eoi))
.unwrap_or(EnvScopeKind::Visible(oi_ident));
asg.index(imm_oi, name, eoi_next);
// TODO: Let's find this a better home.
match eoi_next {
// There is no use in indexing something that will be
// filtered out on retrieval.
EnvScopeKind::Hidden(_) => (),
_ => asg.index(imm_oi, name, eoi_next),
}
Some(eoi_next)
});

View File

@ -57,7 +57,6 @@ use crate::{
use std::iter::once;
use EnvScopeKind::*;
use ObjectTy::*;
const S0: Span = UNKNOWN_SPAN;
@ -74,7 +73,7 @@ macro_rules! assert_scope {
]
) => {
assert_scope(&$asg, $name, [
$( ($obj, $span, $kind(())), )*
$( (ObjectTy::$obj, $span, $kind(())), )*
])
}
}
@ -108,7 +107,7 @@ fn pkg_nested_expr_definition() {
assert_scope!(asg, outer, [
// The identifier is not local,
// and so its scope should extend into the global environment.
// TODO: (Root, S0, Visible),
(Root, S0, Visible),
// Expr does not introduce a new environment,
// and so the innermost environment in which we should be able to
@ -120,7 +119,7 @@ fn pkg_nested_expr_definition() {
assert_scope!(asg, inner, [
// The identifier is not local,
// and so its scope should extend into the global environment.
// TODO: (Root, S0, Visible),
(Root, S0, Visible),
// Expr does not introduce a new environment,
// and so just as the outer expression,
@ -186,7 +185,7 @@ fn pkg_tpl_definition() {
assert_scope!(asg, tpl_outer, [
// The template is defined at the package level,
// and so is incorporated into the global environment.
// TODO: (Root, S0, Visible),
(Root, S0, Visible),
// Definition environment.
(Pkg, m(S1, S20), Visible),
@ -352,7 +351,7 @@ fn assert_scope(
// `tree_reconstruction` omits root,
// so we'll have to add it ourselves.
let oi_root = asg.root(name);
let given = once((Root, S0, asg.lookup_raw(oi_root, name)))
let given = once((ObjectTy::Root, S0, asg.lookup_raw(oi_root, name)))
.chain(given_without_root)
.filter_map(|(ty, span, oeoi)| {
oeoi.map(|eoi| (ty, span, eoi.map(ObjectIndex::cresolve(asg))))

View File

@ -1092,6 +1092,14 @@ mod private {
}
}
impl<OB: ObjectRelatable> From<ObjectIndexToTree<OB>> for ObjectIndexTo<OB> {
fn from(value: ObjectIndexToTree<OB>) -> Self {
match value {
ObjectIndexToTree(oit) => oit,
}
}
}
/// Some [`ObjectIndex`] that can create a _tree_ edge to `OB`.
///
/// This is a specialization of