tamer: asg::air::expr: Introduce RootStrategy

This sets us up to be able to determine how `Dangling` expressions will be
rooted into templates.

This new strategy isn't yet handling `Dangling`; I wanted to get this
committed first so that the `Dangling` refactoring is more clear.

DEV-13708
main
Mike Gerwitz 2023-03-08 11:18:51 -05:00
parent fc1d55c4c5
commit 231296d003
4 changed files with 155 additions and 44 deletions

View File

@ -35,10 +35,9 @@
//! but that would surely result in face-palming and so we're not going
//! air such cringeworthy dad jokes here.
use super::{
graph::object::Pkg,
Asg, AsgError, ObjectIndex,
};
use self::expr::AirExprAggregateReachable;
use super::{graph::object::Pkg, Asg, AsgError, ObjectIndex};
use crate::{
diagnose::Annotate, diagnostic_unreachable, parse::prelude::*,
sym::SymbolId,
@ -65,21 +64,25 @@ pub enum AirAggregate {
Empty,
/// Expecting a package-level token.
PkgHead(ObjectIndex<Pkg>, AirExprAggregate<Pkg>),
PkgHead(ObjectIndex<Pkg>, AirExprAggregateReachable<Pkg>),
/// Parsing an expression.
///
/// 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(ObjectIndex<Pkg>, AirExprAggregate<Pkg>),
PkgExpr(ObjectIndex<Pkg>, AirExprAggregateReachable<Pkg>),
/// Parser is in template parsing mode.
///
/// All objects encountered until the closing [`Air::TplClose`] will be
/// parented to this template rather than the parent [`Pkg`].
/// See [`Air::TplOpen`] for more information.
PkgTpl(ObjectIndex<Pkg>, AirExprAggregate<Pkg>, AirTplAggregate),
PkgTpl(
ObjectIndex<Pkg>,
AirExprAggregateReachable<Pkg>,
AirTplAggregate,
),
}
impl Display for AirAggregate {
@ -254,8 +257,8 @@ impl AirAggregate {
fn delegate_expr(
asg: &mut <Self as ParseState>::Context,
oi_pkg: ObjectIndex<Pkg>,
expr: AirExprAggregate<Pkg>,
etok: impl Into<<AirExprAggregate<Pkg> as ParseState>::Token>,
expr: AirExprAggregateReachable<Pkg>,
etok: impl Into<<AirExprAggregateReachable<Pkg> as ParseState>::Token>,
) -> TransitionResult<Self> {
let tok = etok.into();
let tokspan = tok.span();
@ -292,7 +295,7 @@ impl AirAggregate {
fn delegate_tpl(
asg: &mut <Self as ParseState>::Context,
oi_pkg: ObjectIndex<Pkg>,
stored_expr: AirExprAggregate<Pkg>,
stored_expr: AirExprAggregateReachable<Pkg>,
tplst: AirTplAggregate,
tok: Air,
) -> TransitionResult<Self> {

View File

@ -34,6 +34,23 @@ use crate::{
parse::prelude::*,
span::Span,
};
use std::marker::PhantomData;
#[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<O> = AirExprAggregate<O, ReachableOnly<O>>;
/// 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>>;
/// Parse an AIR expression with binding support.
///
@ -45,19 +62,21 @@ use crate::{
/// handles each of its tokens and performs error recovery on invalid
/// state transitions.
#[derive(Debug, PartialEq)]
pub enum AirExprAggregate<OR: ObjectKind> {
pub enum AirExprAggregate<O: ObjectKind, S: RootStrategy<O>> {
/// Ready for an expression;
/// expression stack is empty.
Ready(ObjectIndex<OR>, ExprStack<Dormant>),
Ready(S, ExprStack<Dormant>, PhantomData<O>),
/// Building an expression.
BuildingExpr(ObjectIndex<OR>, ExprStack<Active>, ObjectIndex<Expr>),
BuildingExpr(S, ExprStack<Active>, ObjectIndex<Expr>),
}
impl<OR: ObjectKind> Display for AirExprAggregate<OR> {
impl<O: ObjectKind, S: RootStrategy<O>> Display for AirExprAggregate<O, S> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Ready(_, es) => write!(f, "ready for expression with {es}"),
Self::Ready(_, es, _) => {
write!(f, "ready for expression with {es}")
}
Self::BuildingExpr(_, es, _) => {
write!(f, "building expression with {es}")
}
@ -65,10 +84,7 @@ impl<OR: ObjectKind> Display for AirExprAggregate<OR> {
}
}
impl<OR: ObjectKind> ParseState for AirExprAggregate<OR>
where
OR: ObjectRelTo<Ident>,
{
impl<O: ObjectKind, S: RootStrategy<O>> ParseState for AirExprAggregate<O, S> {
type Token = AirBindableExpr;
type Object = ();
type Error = AsgError;
@ -84,19 +100,17 @@ where
use AirExprAggregate::*;
match (self, tok) {
(Ready(oi_target, es), AirExpr(ExprOpen(op, span))) => {
(Ready(root, es, _), AirExpr(ExprOpen(op, span))) => {
let oi = asg.create(Expr::new(op, span));
Transition(BuildingExpr(oi_target, es.activate(), oi))
.incomplete()
Transition(BuildingExpr(root, es.activate(), oi)).incomplete()
}
(BuildingExpr(oi_target, es, poi), AirExpr(ExprOpen(op, span))) => {
(BuildingExpr(root, es, poi), AirExpr(ExprOpen(op, span))) => {
let oi = poi.create_subexpr(asg, Expr::new(op, span));
Transition(BuildingExpr(oi_target, es.push(poi), oi))
.incomplete()
Transition(BuildingExpr(root, es.push(poi), oi)).incomplete()
}
(BuildingExpr(oi_target, es, oi), AirExpr(ExprClose(end))) => {
(BuildingExpr(root, es, oi), AirExpr(ExprClose(end))) => {
let start: Span = oi.into();
let _ = oi.map_obj(asg, |expr| {
@ -105,12 +119,11 @@ where
match es.pop() {
(es, Some(poi)) => {
Transition(BuildingExpr(oi_target, es, poi))
.incomplete()
Transition(BuildingExpr(root, es, poi)).incomplete()
}
(es, None) => {
let dangling = es.is_dangling();
let st = Ready(oi_target, es.done());
let st = Ready(root, es.done(), PhantomData::default());
if dangling {
Transition(st).err(AsgError::DanglingExpr(
@ -123,27 +136,24 @@ where
}
}
(BuildingExpr(oi_target, es, oi), AirBind(BindIdent(id))) => {
let oi_ident = asg.lookup_or_missing(id);
oi_target.add_edge_to(asg, oi_ident, None);
(BuildingExpr(root, es, oi), AirBind(BindIdent(id))) => {
let oi_ident = root.defines(asg, id);
// It is important that we do not mark this expression as
// reachable unless we successfully bind the identifier.
match oi_ident.bind_definition(asg, id, oi) {
Ok(_) => Transition(BuildingExpr(
oi_target,
root,
es.reachable_by(oi_ident),
oi,
))
.incomplete(),
Err(e) => {
Transition(BuildingExpr(oi_target, es, oi)).err(e)
}
Err(e) => Transition(BuildingExpr(root, es, oi)).err(e),
}
}
(BuildingExpr(oi_target, es, oi), AirBind(RefIdent(ident))) => {
Transition(BuildingExpr(oi_target, es, oi.ref_expr(asg, ident)))
(BuildingExpr(root, es, oi), AirBind(RefIdent(ident))) => {
Transition(BuildingExpr(root, es, oi.ref_expr(asg, ident)))
.incomplete()
}
@ -166,9 +176,13 @@ where
}
}
impl<O: ObjectKind> AirExprAggregate<O> {
impl<O: ObjectKind, S: RootStrategy<O>> AirExprAggregate<O, S> {
pub(super) fn new_in(oi: ObjectIndex<O>) -> Self {
Self::Ready(oi, ExprStack::default())
Self::Ready(
S::new_root(oi),
ExprStack::default(),
PhantomData::default(),
)
}
}
@ -362,5 +376,95 @@ 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<O: ObjectKind>: Debug + PartialEq {
/// Declare `oi` as the root of all accepted [`Expr`]s produced by
/// the parser.
fn new_root(oi: ObjectIndex<O>) -> 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, 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.
///
/// See [`RootStrategy`] for more information.
#[derive(Debug, PartialEq)]
pub struct ReachableOnly<O: ObjectKind>(ObjectIndex<O>)
where
O: ObjectRelTo<Ident>;
impl<O: ObjectKind> RootStrategy<O> for ReachableOnly<O>
where
O: ObjectRelTo<Ident>,
{
fn new_root(oi: ObjectIndex<O>) -> Self {
Self(oi)
}
fn defines(&self, asg: &mut Asg, id: SPair) -> ObjectIndex<Ident> {
match self {
Self(oi_root) => {
asg.lookup_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).
///
/// See [`RootStrategy`] for more information.
#[derive(Debug, PartialEq)]
pub struct StoreDangling<O: ObjectKind>(ObjectIndex<O>)
where
O: ObjectRelTo<Ident> + ObjectRelTo<Expr>;
impl<O: ObjectKind> RootStrategy<O> for StoreDangling<O>
where
O: ObjectRelTo<Ident> + ObjectRelTo<Expr>,
{
fn new_root(oi: ObjectIndex<O>) -> Self {
Self(oi)
}
fn defines(&self, asg: &mut Asg, id: SPair) -> ObjectIndex<Ident> {
// We are a superset of `ReachableOnly`'s behavior,
// so delegate to avoid duplication.
match self {
Self(oi_root) => ReachableOnly(*oi_root).defines(asg, id),
}
}
}
}
#[cfg(test)]
pub mod test;

View File

@ -26,6 +26,7 @@ use super::{
graph::object::{Pkg, Tpl},
Asg, AsgError, ObjectIndex,
},
expr::AirExprAggregateStoreDangling,
Air, AirExprAggregate,
};
use crate::{
@ -62,7 +63,7 @@ pub enum AirTplAggregate {
BuildingTpl(
ObjectIndex<Pkg>,
ObjectIndex<Tpl>,
AirExprAggregate<Tpl>,
AirExprAggregateStoreDangling<Tpl>,
Option<SPair>,
),
}

View File

@ -22,7 +22,8 @@
use std::fmt::Display;
use super::{
Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy, ObjectRelatable,
Expr, Ident, Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy,
ObjectRelatable,
};
use crate::{asg::Asg, f::Functor, span::Span};
@ -57,9 +58,11 @@ impl Display for Tpl {
}
object_rel! {
/// TODO
/// Templates may expand into nearly any context,
/// and must therefore be able to contain just about anything.
Tpl -> {
// ...
tree Ident,
tree Expr,
}
}