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-13708main
parent
26ddb2ae9d
commit
15fd2de437
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue