tamer: asg::air: Eliminate parent context from AirExprAggregate

This does the same thing to `AirExprAggregate` that was previously done for
`AirAggregate`, taking all parent context from the stack.

This results in a fairly significant simplification of the code, which is
nice, and it makes the `RootStrategy` obviously obsolete in the dangling
case, which will result in more refactoring to simplify it even more.

I regret not taking this route to begin with, but not only was I hoping I
wouldn't need to, but I was still deriving the graph structure and wasn't
sure how this would eventually turn out.  These commits serve as a proof of
necessity.  Or, at least, concrete rationale.

It's worth noting that this also introduces `From` implementations for
`AirAggregate` and the child parsers, and then uses _that_ to push context
from the `AirTplAggregate` parser.  This means that we're just about ready
for it to serve as a superstate.  But there is still a specialization of
`AirExprAggregate` in that `From` impl, which must be removed.

DEV-13708
main
Mike Gerwitz 2023-03-29 11:19:59 -04:00
parent 755c91e04a
commit 525adb8a6c
3 changed files with 143 additions and 113 deletions

View File

@ -39,7 +39,7 @@ use self::expr::AirExprAggregateReachable;
use super::{
graph::object::{ObjectIndexTo, Pkg, Tpl},
Asg, AsgError, Ident, ObjectIndex,
Asg, AsgError, Expr, Ident, ObjectIndex,
};
use crate::{
diagnose::Annotate, diagnostic_todo, parse::prelude::*, sym::SymbolId,
@ -73,7 +73,7 @@ pub enum AirAggregate {
/// This expects to inherit an [`AirExprAggregate`] from the prior state
/// so that we are not continuously re-allocating its stack for each
/// new expression root.
PkgExpr(AirExprAggregateReachable<Pkg>),
PkgExpr(AirExprAggregateReachable),
/// Parser is in template parsing mode.
///
@ -102,6 +102,18 @@ impl Display for AirAggregate {
}
}
impl From<AirExprAggregateReachable> for AirAggregate {
fn from(st: AirExprAggregateReachable) -> Self {
Self::PkgExpr(st)
}
}
impl From<AirTplAggregate> for AirAggregate {
fn from(st: AirTplAggregate) -> Self {
Self::PkgTpl(st)
}
}
impl ParseState for AirAggregate {
type Token = Air;
type Object = ();
@ -160,7 +172,7 @@ impl ParseState for AirAggregate {
(Toplevel(oi_pkg), tok @ AirExpr(..)) => {
ctx.push(Toplevel(oi_pkg));
let expr = AirExprAggregate::new_in(oi_pkg);
let expr = AirExprAggregate::new();
Transition(PkgExpr(expr)).incomplete().with_lookahead(tok)
}
@ -293,8 +305,8 @@ impl AirAggregate {
/// [`crate::parse`] framework.
fn delegate_expr(
ctx: &mut <Self as ParseState>::Context,
expr: AirExprAggregateReachable<Pkg>,
etok: impl Into<<AirExprAggregateReachable<Pkg> as ParseState>::Token>,
expr: AirExprAggregateReachable,
etok: impl Into<<AirExprAggregateReachable as ParseState>::Token>,
) -> TransitionResult<Self> {
let tok = etok.into();
@ -356,9 +368,9 @@ impl AirAggregateCtx {
self.as_mut()
}
fn push(&mut self, st: AirAggregate) {
fn push<S: Into<AirAggregate>>(&mut self, st: S) {
let Self(_, stack) = self;
stack.push(st);
stack.push(st.into());
}
fn pop(&mut self) -> Option<AirAggregate> {
@ -373,9 +385,9 @@ impl AirAggregateCtx {
fn rooting_oi(&self) -> Option<ObjectIndexTo<Ident>> {
let Self(_, stack) = self;
stack.iter().rev().find_map(|st| match *st {
stack.iter().rev().find_map(|st| match st {
AirAggregate::Empty => None,
AirAggregate::Toplevel(pkg_oi) => Some(pkg_oi.into()),
AirAggregate::Toplevel(pkg_oi) => Some((*pkg_oi).into()),
// Expressions never serve as roots for identifiers;
// this will always fall through to the parent context.
@ -383,8 +395,46 @@ impl AirAggregateCtx {
// the next frame should succeed.
AirAggregate::PkgExpr(_) => None,
AirAggregate::PkgTpl(_) => {
diagnostic_todo!(vec![], "PkgTpl rooting_oi")
// 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)
}
})
}
/// The active dangling expression context for [`Expr`]s.
///
/// A value of [`None`] indicates that expressions are not permitted to
/// dangle in the current context
/// (and so must be identified).
fn dangling_expr_oi(&self) -> Option<ObjectIndexTo<Expr>> {
let Self(_, stack) = self;
stack.iter().rev().find_map(|st| match st {
AirAggregate::Empty => 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::Toplevel(_) => 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,
// 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)
}
})
}

View File

@ -31,13 +31,12 @@ use super::{
};
use crate::{
asg::{
graph::object::{ObjectIndexRelTo, ObjectIndexTo, ObjectRelTo},
graph::object::{ObjectIndexRelTo, ObjectIndexTo},
Ident, ObjectKind,
},
f::Functor,
parse::prelude::*,
};
use std::marker::PhantomData;
#[cfg(doc)]
use StackEdge::{Dangling, Reachable};
@ -46,14 +45,13 @@ use StackEdge::{Dangling, Reachable};
/// with expression roots bound to their associated [`Ident`]s.
///
/// See [`ReachableOnly`] for more information.
pub type AirExprAggregateReachable<O> = AirExprAggregate<O, ReachableOnly<O>>;
pub type AirExprAggregateReachable = AirExprAggregate<ReachableOnly>;
/// Parse and aggregate both [`Reachable`] and [`Dangling`] [`Expr`]s into
/// the graph.
///
/// See [`StoreDangling`] for more information.
pub type AirExprAggregateStoreDangling<O> =
AirExprAggregate<O, StoreDangling<O>>;
pub type AirExprAggregateStoreDangling = AirExprAggregate<StoreDangling>;
/// Parse an AIR expression with binding support.
///
@ -65,19 +63,19 @@ pub type AirExprAggregateStoreDangling<O> =
/// handles each of its tokens and performs error recovery on invalid
/// state transitions.
#[derive(Debug, PartialEq)]
pub enum AirExprAggregate<O: ObjectKind, S: RootStrategy<O>> {
pub enum AirExprAggregate<S: RootStrategy> {
/// Ready for an expression;
/// expression stack is empty.
Ready(S, ExprStack<Dormant>, PhantomData<O>),
Ready(S, ExprStack<Dormant>),
/// Building an expression.
BuildingExpr(S, ExprStack<Active>, ObjectIndex<Expr>),
}
impl<O: ObjectKind, S: RootStrategy<O>> Display for AirExprAggregate<O, S> {
impl<S: RootStrategy> Display for AirExprAggregate<S> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Ready(_, es, _) => {
Self::Ready(_, es) => {
write!(f, "ready for expression with {es}")
}
Self::BuildingExpr(_, es, _) => {
@ -87,7 +85,7 @@ impl<O: ObjectKind, S: RootStrategy<O>> Display for AirExprAggregate<O, S> {
}
}
impl<O: ObjectKind, S: RootStrategy<O>> ParseState for AirExprAggregate<O, S> {
impl<S: RootStrategy> ParseState for AirExprAggregate<S> {
type Token = AirBindableExpr;
type Object = ();
type Error = AsgError;
@ -103,7 +101,7 @@ impl<O: ObjectKind, S: RootStrategy<O>> ParseState for AirExprAggregate<O, S> {
use AirExprAggregate::*;
match (self, tok) {
(Ready(root, es, _), AirExpr(ExprStart(op, span))) => {
(Ready(root, es), AirExpr(ExprStart(op, span))) => {
let oi = ctx.asg_mut().create(Expr::new(op, span));
Transition(BuildingExpr(root, es.activate(), oi)).incomplete()
}
@ -119,24 +117,18 @@ impl<O: ObjectKind, S: RootStrategy<O>> ParseState for AirExprAggregate<O, S> {
});
let dangling = es.is_dangling();
let oi_root = ctx.dangling_expr_oi();
match (es.pop(), dangling) {
((es, Some(poi)), _) => {
Transition(BuildingExpr(root, es, poi)).incomplete()
}
((es, None), true) => {
root.hold_dangling(ctx.asg_mut(), oi).transition(Ready(
root,
es.done(),
PhantomData::default(),
))
((es, None), true) => root
.hold_dangling(ctx.asg_mut(), oi_root, oi)
.transition(Ready(root, es.done())),
((es, None), false) => {
Transition(Ready(root, es.done())).incomplete()
}
((es, None), false) => Transition(Ready(
root,
es.done(),
PhantomData::default(),
))
.incomplete(),
}
}
@ -180,13 +172,9 @@ impl<O: ObjectKind, S: RootStrategy<O>> ParseState for AirExprAggregate<O, S> {
}
}
impl<O: ObjectKind, S: RootStrategy<O>> AirExprAggregate<O, S> {
pub(super) fn new_in(oi: ObjectIndex<O>) -> Self {
Self::Ready(
S::new_root(oi),
ExprStack::default(),
PhantomData::default(),
)
impl<S: RootStrategy> AirExprAggregate<S> {
pub(super) fn new() -> Self {
Self::Ready(S::new(), ExprStack::default())
}
}
@ -392,10 +380,10 @@ mod root {
/// for the definition associated with this identifier.
/// An identified expression will be rooted in [`Self`] even if it is a
/// sub-expression.
pub trait RootStrategy<O: ObjectKind>: Debug + PartialEq {
pub trait RootStrategy: Debug + PartialEq {
/// Declare `oi` as the root of all accepted [`Expr`]s produced by
/// the parser.
fn new_root(oi: ObjectIndex<O>) -> Self;
fn new() -> Self;
/// Look up the provided identifier `id` on the [`Asg`] and indicate
/// that its definition is associated with [`Self`]'s root.
@ -417,6 +405,7 @@ mod root {
fn hold_dangling(
&self,
asg: &mut Asg,
oi_root: Option<ObjectIndexTo<Expr>>,
oi_expr: ObjectIndex<Expr>,
) -> Result<(), AsgError>;
}
@ -438,16 +427,11 @@ mod root {
///
/// See [`RootStrategy`] for more information.
#[derive(Debug, PartialEq)]
pub struct ReachableOnly<O: ObjectKind>(ObjectIndex<O>)
where
O: ObjectRelTo<Ident>;
pub struct ReachableOnly;
impl<O: ObjectKind> RootStrategy<O> for ReachableOnly<O>
where
O: ObjectRelTo<Ident>,
{
fn new_root(oi: ObjectIndex<O>) -> Self {
Self(oi)
impl RootStrategy for ReachableOnly {
fn new() -> Self {
Self
}
fn defines(
@ -456,16 +440,14 @@ mod root {
oi_root: ObjectIndexTo<Ident>,
id: SPair,
) -> ObjectIndex<Ident> {
match self {
Self(_oi_root) => asg
.lookup_global_or_missing(id)
.add_edge_from(asg, oi_root, None),
}
asg.lookup_global_or_missing(id)
.add_edge_from(asg, oi_root, None)
}
fn hold_dangling(
&self,
asg: &mut Asg,
_oi_root: Option<ObjectIndexTo<Expr>>,
oi_expr: ObjectIndex<Expr>,
) -> Result<(), AsgError> {
Err(AsgError::DanglingExpr(oi_expr.resolve(asg).span()))
@ -486,41 +468,33 @@ mod root {
///
/// See [`RootStrategy`] for more information.
#[derive(Debug, PartialEq)]
pub struct StoreDangling<O: ObjectKind>(ObjectIndex<O>)
where
O: ObjectRelTo<Ident> + ObjectRelTo<Expr>;
pub struct StoreDangling;
impl<O: ObjectKind> RootStrategy<O> for StoreDangling<O>
where
O: ObjectRelTo<Ident> + ObjectRelTo<Expr>,
{
fn new_root(oi: ObjectIndex<O>) -> Self {
Self(oi)
impl RootStrategy for StoreDangling {
fn new() -> Self {
Self
}
fn defines(
&self,
asg: &mut Asg,
_oi_root: ObjectIndexTo<Ident>,
oi_root: ObjectIndexTo<Ident>,
name: SPair,
) -> ObjectIndex<Ident> {
// This cannot simply call [`ReachableOnly`]'s `defines` because
// we cannot cache in the global environment.
// This can be realized once caching is generalized;
// see the commit that introduced this comment.
match self {
Self(oi_root) => oi_root.declare_local(asg, name),
}
oi_root.declare_local(asg, name)
}
fn hold_dangling(
&self,
asg: &mut Asg,
oi_root: Option<ObjectIndexTo<Expr>>,
oi_expr: ObjectIndex<Expr>,
) -> Result<(), AsgError> {
let Self(oi_root) = self;
oi_root.add_edge_to(asg, oi_expr, None);
oi_root.expect("TODO").add_edge_to(asg, oi_expr, None);
Ok(())
}
}

View File

@ -67,17 +67,13 @@ pub enum AirTplAggregate {
/// tokens that are received in this state are interpreted as directly
/// applying to the template itself,
/// or creating an object directly owned by the template.
Toplevel(TplState, AirExprAggregateStoreDangling<Tpl>),
Toplevel(TplState),
/// Defining a template metavariable.
TplMeta(
TplState,
AirExprAggregateStoreDangling<Tpl>,
ObjectIndex<Meta>,
),
TplMeta(TplState, ObjectIndex<Meta>),
/// Aggregating tokens into a template.
TplExpr(TplState, AirExprAggregateStoreDangling<Tpl>),
TplExpr(TplState, AirExprAggregateStoreDangling),
}
impl Display for AirTplAggregate {
@ -85,11 +81,13 @@ impl Display for AirTplAggregate {
match self {
Self::Ready => write!(f, "ready for template definition"),
Self::Toplevel(tpl, expr) | Self::TplExpr(tpl, expr) => {
Self::Toplevel(tpl) => write!(f, "building {tpl} at toplevel"),
Self::TplExpr(tpl, expr) => {
write!(f, "building {tpl} with {expr}")
}
Self::TplMeta(tpl, _, _) => {
Self::TplMeta(tpl, _) => {
write!(f, "building {tpl} metavariable")
}
}
@ -184,11 +182,7 @@ impl ParseState for AirTplAggregate {
(Ready, AirTpl(TplStart(span))) => {
let oi_tpl = ctx.asg_mut().create(Tpl::new(span));
Transition(Toplevel(
TplState::Dangling(oi_tpl),
AirExprAggregate::new_in(oi_tpl),
))
.incomplete()
Transition(Toplevel(TplState::Dangling(oi_tpl))).incomplete()
}
(Toplevel(..), AirTpl(TplStart(span))) => diagnostic_todo!(
@ -196,7 +190,7 @@ impl ParseState for AirTplAggregate {
"nested tpl open"
),
(Toplevel(tpl, expr), AirBind(BindIdent(id))) => {
(Toplevel(tpl), AirBind(BindIdent(id))) => {
let oi_root = ctx.rooting_oi().expect("TODO");
let asg = ctx.asg_mut();
@ -204,35 +198,31 @@ impl ParseState for AirTplAggregate {
.bind_definition(asg, id, tpl.oi())
.map(|oi_ident| oi_root.defines(asg, oi_ident))
.map(|_| ())
.transition(Toplevel(tpl.identify(id), expr))
.transition(Toplevel(tpl.identify(id)))
}
(Toplevel(tpl, expr), AirBind(RefIdent(id))) => {
(Toplevel(tpl), AirBind(RefIdent(id))) => {
tpl.oi().apply_named_tpl(ctx.asg_mut(), id);
Transition(Toplevel(tpl, expr)).incomplete()
Transition(Toplevel(tpl)).incomplete()
}
(Toplevel(tpl, expr), AirTpl(TplMetaStart(span))) => {
(Toplevel(tpl), AirTpl(TplMetaStart(span))) => {
let oi_meta = ctx.asg_mut().create(Meta::new_required(span));
Transition(TplMeta(tpl, expr, oi_meta)).incomplete()
Transition(TplMeta(tpl, oi_meta)).incomplete()
}
(TplMeta(tpl, expr, oi_meta), AirTpl(TplMetaEnd(cspan))) => {
(TplMeta(tpl, oi_meta), AirTpl(TplMetaEnd(cspan))) => {
oi_meta.close(ctx.asg_mut(), cspan);
Transition(Toplevel(tpl, expr)).incomplete()
Transition(Toplevel(tpl)).incomplete()
}
(TplMeta(tpl, expr, oi_meta), AirTpl(TplLexeme(lexeme))) => {
Transition(TplMeta(
tpl,
expr,
oi_meta.assign_lexeme(ctx.asg_mut(), lexeme),
))
.incomplete()
}
(TplMeta(tpl, oi_meta), AirTpl(TplLexeme(lexeme))) => Transition(
TplMeta(tpl, oi_meta.assign_lexeme(ctx.asg_mut(), lexeme)),
)
.incomplete(),
(TplMeta(tpl, expr, oi_meta), AirBind(BindIdent(name))) => {
(TplMeta(tpl, oi_meta), AirBind(BindIdent(name))) => {
oi_meta.identify_as_tpl_param(ctx.asg_mut(), tpl.oi(), name);
Transition(TplMeta(tpl, expr, oi_meta)).incomplete()
Transition(TplMeta(tpl, oi_meta)).incomplete()
}
(TplMeta(..), tok @ AirBind(RefIdent(..))) => {
@ -273,13 +263,14 @@ impl ParseState for AirTplAggregate {
)
}
(Toplevel(tpl, _expr_done), AirTpl(TplEnd(span))) => {
(Toplevel(tpl), AirTpl(TplEnd(span))) => {
tpl.close(ctx.asg_mut(), span).transition(Ready)
}
(TplExpr(tpl, expr), AirTpl(TplEnd(span))) => {
// TODO: duplicated with AirAggregate
if expr.is_accepting(ctx) {
ctx.pop();
tpl.close(ctx.asg_mut(), span).transition(Ready)
} else {
Transition(TplExpr(tpl, expr))
@ -287,11 +278,11 @@ impl ParseState for AirTplAggregate {
}
}
(Toplevel(tpl, expr_done), AirTpl(TplEndRef(span))) => {
(Toplevel(tpl), AirTpl(TplEndRef(span))) => {
let oi_target = ctx.expansion_oi().expect("TODO");
tpl.oi().expand_into(ctx.asg_mut(), oi_target);
Transition(Toplevel(tpl.anonymous_reachable(), expr_done))
Transition(Toplevel(tpl.anonymous_reachable()))
.incomplete()
.with_lookahead(AirTpl(TplEnd(span)))
}
@ -306,8 +297,12 @@ impl ParseState for AirTplAggregate {
.with_lookahead(AirTpl(TplEnd(span)))
}
(Toplevel(tpl, expr), AirExpr(etok)) => {
Self::delegate_expr(ctx, tpl, expr, etok)
(Toplevel(tpl), tok @ AirExpr(_)) => {
ctx.push(Toplevel(tpl));
Transition(TplExpr(tpl, AirExprAggregate::new()))
.incomplete()
.with_lookahead(tok)
}
(TplExpr(tpl, expr), AirExpr(etok)) => {
@ -355,17 +350,28 @@ impl AirTplAggregate {
Self::Ready
}
pub(super) fn active_tpl_oi(&self) -> Option<ObjectIndex<Tpl>> {
use AirTplAggregate::*;
match self {
Ready => None,
Toplevel(tplst) | TplMeta(tplst, _) | TplExpr(tplst, _) => {
Some(tplst.oi())
}
}
}
/// Delegate to the expression parser [`AirExprAggregate`].
fn delegate_expr(
asg: &mut <Self as ParseState>::Context,
tpl: TplState,
expr: AirExprAggregateStoreDangling<Tpl>,
etok: impl Into<<AirExprAggregateStoreDangling<Tpl> as ParseState>::Token>,
expr: AirExprAggregateStoreDangling,
etok: impl Into<<AirExprAggregateStoreDangling as ParseState>::Token>,
) -> TransitionResult<Self> {
let tok = etok.into();
expr.parse_token(tok, asg).branch_dead::<Self, _>(
|expr, ()| Transition(Self::Toplevel(tpl, expr)).incomplete(),
|_, ()| Transition(Self::Toplevel(tpl)).incomplete(),
|expr, result, ()| {
result
.map(ParseStatus::reflexivity)