tamer: asg::air::expr: Eliminate RootStrategy

I love deleting code I just wrote...

This doesn't solve the underlying problems with identifiers, but it does at
least lift it into the `AirAggregateCtx`, allowing `AirExprAggregate` to be
even further simplified.  Now the `From` implementation is not specialized
and we can readily convert to a SuperState.

There's still a lot of TODOs here, though.  And some of them will
unfortunately require runtime checks where there was previously a
compile-time check.  But that's okay in a lot of the cases, because the
empty behavior will replace existing error checks.

DEV-13708
main
Mike Gerwitz 2023-03-29 13:49:05 -04:00
parent 26ddb2ae9d
commit 15fd2de437
3 changed files with 63 additions and 160 deletions

View File

@ -35,10 +35,8 @@
//! but that would surely result in face-palming and so we're not going
//! air such cringeworthy dad jokes here.
use self::expr::AirExprAggregateReachable;
use super::{
graph::object::{ObjectIndexTo, Pkg, Tpl},
graph::object::{ObjectIndexRelTo, ObjectIndexTo, Pkg, Tpl},
Asg, AsgError, Expr, Ident, ObjectIndex,
};
use crate::{
@ -73,7 +71,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),
PkgExpr(AirExprAggregate),
/// Parser is in template parsing mode.
///
@ -102,8 +100,8 @@ impl Display for AirAggregate {
}
}
impl From<AirExprAggregateReachable> for AirAggregate {
fn from(st: AirExprAggregateReachable) -> Self {
impl From<AirExprAggregate> for AirAggregate {
fn from(st: AirExprAggregate) -> Self {
Self::PkgExpr(st)
}
}
@ -305,8 +303,8 @@ impl AirAggregate {
/// [`crate::parse`] framework.
fn delegate_expr(
ctx: &mut <Self as ParseState>::Context,
expr: AirExprAggregateReachable,
etok: impl Into<<AirExprAggregateReachable as ParseState>::Token>,
expr: AirExprAggregate,
etok: impl Into<<AirExprAggregate as ParseState>::Token>,
) -> TransitionResult<Self> {
let tok = etok.into();
@ -457,6 +455,32 @@ impl AirAggregateCtx {
}
})
}
/// Root an identifier using the [`Self::rooting_oi`] atop of the stack.
///
/// Until [`Asg`] can be further generalized,
/// there are unfortunately two rooting strategies employed:
///
/// 1. If the stack has only a single held frame,
/// then it is assumed to be the package representing the active
/// compilation unit and the identifier is indexed in the global
/// scope.
/// 2. Otherwise,
/// the identifier is defined locally and does not undergo
/// indexing.
///
/// TODO: Generalize this.
fn defines(&mut self, name: SPair) -> ObjectIndex<Ident> {
let oi_root = self.rooting_oi().expect("TODO");
let Self(asg, stack) = self;
match stack.len() {
1 => asg
.lookup_global_or_missing(name)
.add_edge_from(asg, oi_root, None),
_ => oi_root.declare_local(asg, name),
}
}
}
impl AsRef<Asg> for AirAggregateCtx {

View File

@ -32,7 +32,7 @@ use super::{
use crate::{
asg::{
graph::object::{ObjectIndexRelTo, ObjectIndexTo},
Ident, ObjectKind,
ObjectKind,
},
f::Functor,
parse::prelude::*,
@ -41,18 +41,6 @@ use crate::{
#[cfg(doc)]
use StackEdge::{Dangling, Reachable};
/// Parse and aggregate [`Reachable`] [`Expr`]s into the graph,
/// with expression roots bound to their associated [`Ident`]s.
///
/// See [`ReachableOnly`] for more information.
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 = AirExprAggregate<StoreDangling>;
/// Parse an AIR expression with binding support.
///
/// Expressions are composable,
@ -63,29 +51,29 @@ pub type AirExprAggregateStoreDangling = AirExprAggregate<StoreDangling>;
/// handles each of its tokens and performs error recovery on invalid
/// state transitions.
#[derive(Debug, PartialEq)]
pub enum AirExprAggregate<S: RootStrategy> {
pub enum AirExprAggregate {
/// Ready for an expression;
/// expression stack is empty.
Ready(S, ExprStack<Dormant>),
Ready(ExprStack<Dormant>),
/// Building an expression.
BuildingExpr(S, ExprStack<Active>, ObjectIndex<Expr>),
BuildingExpr(ExprStack<Active>, ObjectIndex<Expr>),
}
impl<S: RootStrategy> Display for AirExprAggregate<S> {
impl Display for AirExprAggregate {
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, _) => {
Self::BuildingExpr(es, _) => {
write!(f, "building expression with {es}")
}
}
}
}
impl<S: RootStrategy> ParseState for AirExprAggregate<S> {
impl ParseState for AirExprAggregate {
type Token = AirBindableExpr;
type Object = ();
type Error = AsgError;
@ -101,17 +89,17 @@ impl<S: RootStrategy> ParseState for AirExprAggregate<S> {
use AirExprAggregate::*;
match (self, tok) {
(Ready(root, es), AirExpr(ExprStart(op, span))) => {
(Ready(es), AirExpr(ExprStart(op, span))) => {
let oi = ctx.asg_mut().create(Expr::new(op, span));
Transition(BuildingExpr(root, es.activate(), oi)).incomplete()
Transition(BuildingExpr(es.activate(), oi)).incomplete()
}
(BuildingExpr(root, es, poi), AirExpr(ExprStart(op, span))) => {
(BuildingExpr(es, poi), AirExpr(ExprStart(op, span))) => {
let oi = poi.create_subexpr(ctx.asg_mut(), Expr::new(op, span));
Transition(BuildingExpr(root, es.push(poi), oi)).incomplete()
Transition(BuildingExpr(es.push(poi), oi)).incomplete()
}
(BuildingExpr(root, es, oi), AirExpr(ExprEnd(end))) => {
(BuildingExpr(es, oi), AirExpr(ExprEnd(end))) => {
let _ = oi.map_obj(ctx.asg_mut(), |expr| {
expr.map(|span| span.merge(end).unwrap_or(span))
});
@ -121,42 +109,35 @@ impl<S: RootStrategy> ParseState for AirExprAggregate<S> {
match (es.pop(), dangling) {
((es, Some(poi)), _) => {
Transition(BuildingExpr(root, es, poi)).incomplete()
Transition(BuildingExpr(es, poi)).incomplete()
}
((es, None), true) => {
Self::hold_dangling(ctx.asg_mut(), oi_root, oi)
.transition(Ready(root, es.done()))
.transition(Ready(es.done()))
}
((es, None), false) => {
Transition(Ready(root, es.done())).incomplete()
Transition(Ready(es.done())).incomplete()
}
}
}
(BuildingExpr(root, es, oi), AirBind(BindIdent(id))) => {
let oi_root = ctx.rooting_oi().expect("TODO");
let oi_ident = root.defines(ctx.asg_mut(), oi_root, id);
(BuildingExpr(es, oi), AirBind(BindIdent(id))) => {
let oi_ident = ctx.defines(id);
// It is important that we do not mark this expression as
// reachable unless we successfully bind the identifier.
match oi_ident.bind_definition(ctx.asg_mut(), id, oi) {
Ok(_) => Transition(BuildingExpr(
root,
es.reachable_by(oi_ident),
oi,
))
.incomplete(),
Err(e) => Transition(BuildingExpr(root, es, oi)).err(e),
Ok(_) => {
Transition(BuildingExpr(es.reachable_by(oi_ident), oi))
.incomplete()
}
Err(e) => Transition(BuildingExpr(es, oi)).err(e),
}
}
(BuildingExpr(root, es, oi), AirBind(RefIdent(ident))) => {
Transition(BuildingExpr(
root,
es,
oi.ref_expr(ctx.asg_mut(), ident),
))
.incomplete()
(BuildingExpr(es, oi), AirBind(RefIdent(ident))) => {
Transition(BuildingExpr(es, oi.ref_expr(ctx.asg_mut(), ident)))
.incomplete()
}
(st @ Ready(..), AirExpr(ExprEnd(span))) => {
@ -173,9 +154,9 @@ impl<S: RootStrategy> ParseState for AirExprAggregate<S> {
}
}
impl<S: RootStrategy> AirExprAggregate<S> {
impl AirExprAggregate {
pub(super) fn new() -> Self {
Self::Ready(S::new(), ExprStack::default())
Self::Ready(ExprStack::default())
}
/// Hold or reject a [`Dangling`] root [`Expr`].
@ -385,106 +366,5 @@ impl Display for ExprStack<Active> {
}
}
pub use root::*;
mod root {
use super::*;
use std::fmt::Debug;
/// The rooting strategy to employ after an [`Expr`] construction.
///
/// The method [`Self::defines`] roots an identifier,
/// stating that the object associated with [`Self`] is responsible
/// 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: Debug + PartialEq {
/// Declare `oi` as the root of all accepted [`Expr`]s produced by
/// the parser.
fn new() -> Self;
/// Look up the provided identifier `id` on the [`Asg`] and indicate
/// that its definition is associated with [`Self`]'s root.
///
/// This is invoked for _all_ identifiers,
/// including sub-expressions.
fn defines(
&self,
asg: &mut Asg,
oi_root: ObjectIndexTo<Ident>,
id: SPair,
) -> ObjectIndex<Ident>;
}
/// Accept and root only [`Reachable`] root expressions.
///
/// Note that a root expresion is still [`Dangling`]
/// (and therefore not [`Reachable`])
/// even if one of its sub-expressions has been bound to an
/// identifier.
/// In that case,
/// the sub-expression will be rooted in [`Self`],
/// but the [`Dangling`] root expression will still be rejected.
///
/// This expects identifiers to be rooted in the global environment,
/// which is the package representing the active compilation unit.
/// This may be relaxed once identifier caching is generalized;
/// at the time of writing it is too coupled to the graph root.
///
/// See [`RootStrategy`] for more information.
#[derive(Debug, PartialEq)]
pub struct ReachableOnly;
impl RootStrategy for ReachableOnly {
fn new() -> Self {
Self
}
fn defines(
&self,
asg: &mut Asg,
oi_root: ObjectIndexTo<Ident>,
id: SPair,
) -> ObjectIndex<Ident> {
asg.lookup_global_or_missing(id)
.add_edge_from(asg, oi_root, None)
}
}
/// Accept both [`Reachable`] and [`Dangling`] expressions.
///
/// A [`Dangling`] expression will have the [`Expr`] rooted instead of
/// an [`Ident`].
///
/// Sub-expressions can be thought of as utilizing this strategy with an
/// implicit parent [`ObjectIndex<Expr>`](ObjectIndex).
///
/// Unlike [`ReachableOnly`],
/// this does _not_ cache identifiers in the global environment.
/// See there for more information.
///
/// See [`RootStrategy`] for more information.
#[derive(Debug, PartialEq)]
pub struct StoreDangling;
impl RootStrategy for StoreDangling {
fn new() -> Self {
Self
}
fn defines(
&self,
asg: &mut Asg,
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.
oi_root.declare_local(asg, name)
}
}
}
#[cfg(test)]
pub mod test;

View File

@ -23,7 +23,6 @@
use super::{
super::{graph::object::Tpl, Asg, AsgError, ObjectIndex},
expr::AirExprAggregateStoreDangling,
ir::AirTemplatable,
AirAggregateCtx, AirExprAggregate,
};
@ -73,7 +72,7 @@ pub enum AirTplAggregate {
TplMeta(TplState, ObjectIndex<Meta>),
/// Aggregating tokens into a template.
TplExpr(TplState, AirExprAggregateStoreDangling),
TplExpr(TplState, AirExprAggregate),
}
impl Display for AirTplAggregate {
@ -365,8 +364,8 @@ impl AirTplAggregate {
fn delegate_expr(
asg: &mut <Self as ParseState>::Context,
tpl: TplState,
expr: AirExprAggregateStoreDangling,
etok: impl Into<<AirExprAggregateStoreDangling as ParseState>::Token>,
expr: AirExprAggregate,
etok: impl Into<<AirExprAggregate as ParseState>::Token>,
) -> TransitionResult<Self> {
let tok = etok.into();