tamer: parse::util::expand::StitchExpansion: Began transition from ParseState to method
My initial plan with expansion was to wrap a `PasteState` in another that unwraps `Expansion` and converts into a `Dead` state, so that existing `TransitionResult` stitching methods (`delegate`, specifically) could be used. But the desire to use that existing method was primarily because stitching was a complex operation that was abstracted away _as part of the `delegate` method_, which made writing new ones verbose and difficult. Thus began the previous commits to begin to move that responsibility elsewhere so that it could be more composable. This continues with that, introducing a new trait that will culminate in the removal of a wrapping `ParseState` in favor of a stitching method. The old `StitchableExpansionState` is still used for tests, which demonstrates that the boilerplate problem still exists despite improvements made here These will become more generalized in the future as I have time (and the functional aspects of the code more formalized too, now that they're taking shape). The benefit of this is that we avoid having to warp our abstractions in ways that don't make sense (use of a dead state transition) just to satisfy existing APIs. It also means that we do not need the boilerplate of a `ParseState` any time we want to introduce this type of stitching/delegation. It also means that those methods can eventually be extracted into more general traits in the future as well. Ultimately, though, the two would have accomplished the same thing. But the difference is most emphasized in the _parent_---the actual stitching still has to take place for desugaring in the attribute parser, and I'd like for that abstraction to still be in terms of expansion. But if I utilized `StitchableExpansionState`, which converted into a dead state, I'd have to either forego the expansion abstraction---which would make the parser even more confusing---or I'd have to create _another_ abstraction around the dead state, which would mean that I stripped one abstraction just to introduce another one that's essentially the same thing. It didn't feel right, but it would have worked. The use of `PhantomData` in `StitchableExpansionState` was also a sign that something wasn't quite right, in terms of how the abstractions were integrating with one-another. And so here we are, as I struggle to wade my way through all of the yak shavings and make any meaningful progress on this project, while others continue to suffer due to slow build times. I'm sorry. Even if the system is improving. DEV-13156main
parent
1ce36225f6
commit
1aca0945df
|
@ -225,6 +225,40 @@ impl<S: ParseState> TransitionResult<S> {
|
|||
Result(result, la) => falive(st, result, la),
|
||||
}
|
||||
}
|
||||
|
||||
/// Conditionally map to a [`TransitionResult`] based on whether the
|
||||
/// inner [`TransitionData`] represents an object.
|
||||
pub(in super::super) fn branch_obj_la<SB: ParseState>(
|
||||
self,
|
||||
fobj: impl FnOnce(
|
||||
Transition<S>,
|
||||
<S as ParseState>::Object,
|
||||
Option<Lookahead<<S as ParseState>::Token>>,
|
||||
) -> TransitionResult<<SB as ParseState>::Super>,
|
||||
fother: impl FnOnce(Transition<S>) -> Transition<SB>,
|
||||
) -> TransitionResult<<SB as ParseState>::Super>
|
||||
where
|
||||
S: PartiallyStitchableParseState<SB>,
|
||||
{
|
||||
use ParseStatus::{Incomplete, Object};
|
||||
use TransitionData::{Dead, Result};
|
||||
|
||||
let Self(st, data) = self;
|
||||
|
||||
match data {
|
||||
Result(Ok(Object(obj)), la) => fobj(st, obj, la).into_super(),
|
||||
|
||||
// Can't use `TransitionData::inner_into` since we only have a
|
||||
// `PartiallyStitchableParseState`,
|
||||
// and `into_inner` requires being able to convert the inner
|
||||
// object that we handled above.
|
||||
Result(Ok(Incomplete), la) => {
|
||||
fother(st).incomplete().maybe_with_lookahead(la)
|
||||
}
|
||||
Result(Err(e), la) => fother(st).err(e).maybe_with_lookahead(la),
|
||||
Dead(Lookahead(la)) => fother(st).dead(la),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Token to use as a lookahead token in place of the next token from the
|
||||
|
@ -350,48 +384,6 @@ impl<S: ParseState> TransitionData<S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Map [`TransitionData`] when the inner result is of type
|
||||
/// [`ParseStatus::Object`].
|
||||
///
|
||||
/// This will map over `self` within the context of an inner
|
||||
/// [`ParseStatus::Object`] and an associated optional token of
|
||||
/// [`Lookahead`].
|
||||
/// This allows using objects to influence parser operations more
|
||||
/// broadly.
|
||||
///
|
||||
/// _This method is private to this module because it requires that the
|
||||
/// caller be diligent in not discarding the provided token of
|
||||
/// lookahead._
|
||||
/// Since this token may be stored and later emitted,
|
||||
/// there is no reliable automated way at present to ensure that this
|
||||
/// invariant is upheld;
|
||||
/// such an effort is far beyond the scope of current work at the
|
||||
/// time of writing.
|
||||
pub(in super::super) fn map_when_obj<SB: ParseState>(
|
||||
self,
|
||||
f: impl FnOnce(S::Object, Option<Lookahead<S::Token>>) -> TransitionData<SB>,
|
||||
) -> TransitionData<SB>
|
||||
where
|
||||
SB: ParseState<Token = S::Token, Error = S::Error>,
|
||||
{
|
||||
// Ideally this will be decomposed into finer-grained functions
|
||||
// (as in a more traditional functional style),
|
||||
// but such wasn't needed at the time of writing.
|
||||
// But this is dizzying.
|
||||
match self {
|
||||
TransitionData::Result(Ok(ParseStatus::Object(obj)), la) => {
|
||||
f(obj, la)
|
||||
}
|
||||
TransitionData::Result(Ok(ParseStatus::Incomplete), la) => {
|
||||
TransitionData::Result(Ok(ParseStatus::Incomplete), la)
|
||||
}
|
||||
TransitionData::Result(Err(e), la) => {
|
||||
TransitionData::Result(Err(e), la)
|
||||
}
|
||||
TransitionData::Dead(la) => TransitionData::Dead(la),
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts a reflexive relationship between the [`TransitionData`] of
|
||||
/// our own [`ParseState`] `S` and a target [`ParseState`] `SB`.
|
||||
///
|
||||
|
@ -575,25 +567,17 @@ impl<S: ParseState> Transition<S> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Map over the inner [`ParseState`] `S` to another
|
||||
/// Produce a map over the inner [`ParseState`] `S` to another
|
||||
/// [`ParseState`] `SB`.
|
||||
///
|
||||
/// Unlike other parts of this API which mandate explicit instantiation
|
||||
/// of [`Transition`] for self-documentation,
|
||||
/// this maps over the inner value since [`Transition`] is already
|
||||
/// apparent.
|
||||
/// This is consequently much less verbose,
|
||||
/// as it allows using tuple constructions for `f`,
|
||||
/// and most [`ParseState`]s are implemented as tuples
|
||||
/// (or tuple enums)
|
||||
/// in practice.
|
||||
pub fn map<SB: ParseState>(
|
||||
self,
|
||||
f: impl FnOnce(S) -> SB,
|
||||
) -> Transition<SB> {
|
||||
match self {
|
||||
Self(st) => Transition(f(st)),
|
||||
}
|
||||
/// Note that this is a curried associated function,
|
||||
/// not a method.
|
||||
/// The intent is to maintain self-documentation by invoking it
|
||||
/// qualified as [`Transition::fmap`].
|
||||
pub fn fmap<SB: ParseState>(
|
||||
f: impl Fn(S) -> SB,
|
||||
) -> impl Fn(Transition<S>) -> Transition<SB> {
|
||||
move |Self(st)| Transition(f(st))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,12 +21,21 @@
|
|||
//!
|
||||
//! _Expansion_ refers to the production of many [`Object`]s that are
|
||||
//! derived from a single [`Token`].
|
||||
//! An [`ExpandableParseState`] is a [`ClosedParseState`] that,
|
||||
//! provided a [`Token`],
|
||||
//! produces an [`Expansion`] of zero or more [`Expansion::Expanded`]
|
||||
//! [`Object`]s before terminating with a [`Expansion::DoneExpanding`]
|
||||
//! [`Token`] intended to replace the originally provided [`Token`].
|
||||
//!
|
||||
//! An [`ExpandableParseState`] can be stitched with a parent parser using
|
||||
//! [`StitchExpansion`],
|
||||
//! giving the perception of expanding into that parent's token stream.
|
||||
|
||||
use super::super::{
|
||||
prelude::*,
|
||||
state::{Lookahead, StitchableParseState, TransitionData},
|
||||
use super::super::{prelude::*, state::Lookahead};
|
||||
use crate::{
|
||||
diagnose::{panic::DiagnosticOptionPanic, Annotate},
|
||||
parse::state::PartiallyStitchableParseState,
|
||||
};
|
||||
use std::{fmt::Display, marker::PhantomData};
|
||||
|
||||
/// Represents an expansion operation on some source token of type `T`.
|
||||
///
|
||||
|
@ -39,6 +48,14 @@ pub enum Expansion<T, O: Object> {
|
|||
|
||||
/// Expansion is complete and the source token should be replaced with
|
||||
/// the inner `T`.
|
||||
///
|
||||
/// Since the expectation is that the parser has completed parsing and
|
||||
/// no longer requires the token provided to it,
|
||||
/// the parser yielding this variant _must not_ yield a token of
|
||||
/// lookahead,
|
||||
/// otherwise the system assume that the parser has an
|
||||
/// implementation defect (bug) and will be forced to panic rather
|
||||
/// than discard it.
|
||||
DoneExpanding(T),
|
||||
}
|
||||
|
||||
|
@ -55,116 +72,73 @@ where
|
|||
|
||||
/// An [`ExpandableParseState`] capable of expanding into the token stream
|
||||
/// of a parent [`ParseState`] `SP`.
|
||||
///
|
||||
/// This trait asserts that an [`ExpandableParseState`] is a
|
||||
/// [`StitchableParseState<SP>`](StitchableParseState) after being wrapped
|
||||
/// by [`StitchableExpansionState`].
|
||||
pub trait ExpandableInto<SP: ParseState> =
|
||||
ExpandableParseState<<SP as ParseState>::Object>
|
||||
where
|
||||
StitchableExpansionState<Self, <SP as ParseState>::Object>:
|
||||
StitchableParseState<SP>;
|
||||
Self: ExpandableParseState<<SP as ParseState>::Object>
|
||||
+ PartiallyStitchableParseState<SP>;
|
||||
|
||||
/// Convert a [`ClosedParseState`] yielding an [`Expansion<T,O>`](Expansion)
|
||||
/// object into a parser yielding `O` with a dead state yielding `T`.
|
||||
/// [`ExpandableParseState`] state stitching.
|
||||
///
|
||||
/// It is more convenient and clear to write parsers using [`Expansion`],
|
||||
/// since those variants not only state directly what the intent of the
|
||||
/// operations are,
|
||||
/// but also avoid having to work with dead states.
|
||||
/// However,
|
||||
/// their wrapping in [`Expansion`] makes them difficult to delegate to
|
||||
/// (compose with)
|
||||
/// other parsers using [`ParseState`]'s `delegate_*` family of
|
||||
/// functions.
|
||||
///
|
||||
/// This parser handles this translation by stripping away the
|
||||
/// [`Expansion`] abstraction and producing a [`ParseState`] that looks
|
||||
/// and acts like what would have been implemented in the absence of such
|
||||
/// an abstraction.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct StitchableExpansionState<S: ClosedParseState, O: Object> {
|
||||
st: S,
|
||||
_phantom: PhantomData<O>,
|
||||
}
|
||||
|
||||
// We implement Default if the parser `S` that we're wrapping does.
|
||||
impl<S: ClosedParseState, O: Object> Default for StitchableExpansionState<S, O>
|
||||
where
|
||||
S: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
st: Default::default(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ClosedParseState, O: Object> ParseState
|
||||
for StitchableExpansionState<S, O>
|
||||
where
|
||||
S: ExpandableParseState<O>,
|
||||
{
|
||||
type Token = S::Token;
|
||||
type Object = O;
|
||||
type Error = S::Error;
|
||||
type Context = S::Context;
|
||||
|
||||
#[inline]
|
||||
fn parse_token(
|
||||
/// See [`Self::stitch_expansion`] for more information.
|
||||
pub trait StitchExpansion: ClosedParseState {
|
||||
/// Stitch a [`ExpandableParseState`] that is
|
||||
/// [`ExpandableInto<SP>`](ExpandableInto).
|
||||
///
|
||||
/// This combines the state machine of an [`ExpandableParseState`],
|
||||
/// allowing that parser to expand into the token stream of [`Self`].
|
||||
///
|
||||
/// Panics
|
||||
/// ======
|
||||
/// This will panic with diagnostic information if a token of lookahead
|
||||
/// is provided with a [`Expansion::DoneExpanding`] variant.
|
||||
/// See that variant for more information.
|
||||
fn stitch_expansion<SP: ParseState, C>(
|
||||
self,
|
||||
tok: Self::Token,
|
||||
ctx: &mut Self::Context,
|
||||
) -> TransitionResult<Self::Super> {
|
||||
use Expansion::*;
|
||||
tok: <Self as ParseState>::Token,
|
||||
mut ctx: C,
|
||||
into: impl Fn(Transition<Self>) -> Transition<SP>,
|
||||
done: impl FnOnce(
|
||||
Transition<Self>,
|
||||
<SP as ParseState>::Token,
|
||||
) -> TransitionResult<SP>,
|
||||
) -> TransitionResult<<SP as ParseState>::Super>
|
||||
where
|
||||
Self: ExpandableInto<SP>,
|
||||
C: AsMut<<Self as ParseState>::Context>,
|
||||
{
|
||||
use Expansion::{DoneExpanding, Expanded};
|
||||
|
||||
let Self { st, _phantom } = self;
|
||||
self.parse_token(tok, ctx.as_mut()).branch_obj_la(
|
||||
|st, obj, la| match (obj, la) {
|
||||
(Expanded(obj), la) => {
|
||||
into(st).ok(obj).maybe_with_lookahead(la)
|
||||
}
|
||||
|
||||
st.parse_token(tok, ctx).bimap(
|
||||
|st| Self { st, _phantom },
|
||||
|data| {
|
||||
data.map_when_obj(|obj, la| match (obj, la) {
|
||||
(Expanded(obj), la) => {
|
||||
TransitionData::Result(Ok(ParseStatus::Object(obj)), la)
|
||||
}
|
||||
|
||||
// Since we are converting the `DoneExpanding` variant
|
||||
// into a lookahead token,
|
||||
// we would have nothing to do with a token of
|
||||
// lookahead if one were provided to us.
|
||||
(DoneExpanding(tok), Some(la)) => la.overwrite_panic(
|
||||
tok,
|
||||
(DoneExpanding(tok), la) => {
|
||||
// Uphold parser lookahead invariant.
|
||||
la.diagnostic_expect_none(
|
||||
|Lookahead(la_tok)| {
|
||||
vec![
|
||||
la_tok.span().note(
|
||||
"this token of lookahead would be lost",
|
||||
),
|
||||
tok.span().internal_error(
|
||||
"unexpected token of lookahead while \
|
||||
completing expansion of this token",
|
||||
),
|
||||
]
|
||||
},
|
||||
"cannot provide lookahead token with \
|
||||
Expansion::DoneExpanding",
|
||||
),
|
||||
);
|
||||
|
||||
(DoneExpanding(tok), None) => {
|
||||
TransitionData::Dead(Lookahead(tok))
|
||||
}
|
||||
})
|
||||
done(st, tok).into_super()
|
||||
}
|
||||
},
|
||||
&into,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_accepting(&self, ctx: &Self::Context) -> bool {
|
||||
self.st.is_accepting(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ClosedParseState, O: Object> Display
|
||||
for StitchableExpansionState<S, O>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self {
|
||||
st: parser,
|
||||
_phantom,
|
||||
} => {
|
||||
write!(f, "{parser}, with Expansion stripped")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -23,7 +23,75 @@ use crate::{
|
|||
span::{dummy::*, Span},
|
||||
sym::{st::raw, SymbolId},
|
||||
};
|
||||
use std::{assert_matches::assert_matches, convert::Infallible};
|
||||
use std::{
|
||||
assert_matches::assert_matches, convert::Infallible, fmt::Display,
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct StitchableExpansionState<S: ClosedParseState, O: Object> {
|
||||
st: S,
|
||||
_phantom: PhantomData<O>,
|
||||
}
|
||||
|
||||
impl<S: ClosedParseState, O: Object> Default for StitchableExpansionState<S, O>
|
||||
where
|
||||
S: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
st: Default::default(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ClosedParseState, O: Object> ParseState
|
||||
for StitchableExpansionState<S, O>
|
||||
where
|
||||
S: ExpandableParseState<O> + StitchExpansion,
|
||||
<S as ParseState>::Context: AsMut<<S as ParseState>::Context>,
|
||||
{
|
||||
type Token = S::Token;
|
||||
type Object = O;
|
||||
type Error = S::Error;
|
||||
type Context = S::Context;
|
||||
|
||||
#[inline]
|
||||
fn parse_token(
|
||||
self,
|
||||
tok: Self::Token,
|
||||
ctx: &mut Self::Context,
|
||||
) -> TransitionResult<Self::Super> {
|
||||
let Self { st, _phantom } = self;
|
||||
|
||||
st.stitch_expansion(
|
||||
tok,
|
||||
ctx,
|
||||
Transition::fmap(|st| Self { st, _phantom }),
|
||||
|Transition(st), tok| Transition(Self { st, _phantom }).dead(tok),
|
||||
)
|
||||
}
|
||||
|
||||
fn is_accepting(&self, ctx: &Self::Context) -> bool {
|
||||
self.st.is_accepting(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ClosedParseState, O: Object> Display
|
||||
for StitchableExpansionState<S, O>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self {
|
||||
st: parser,
|
||||
_phantom,
|
||||
} => {
|
||||
write!(f, "{parser}, with Expansion stripped")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct TestObject(SPair);
|
||||
|
@ -94,6 +162,8 @@ impl Display for TestParseState {
|
|||
}
|
||||
}
|
||||
|
||||
impl StitchExpansion for TestParseState {}
|
||||
|
||||
const STOP: SymbolId = raw::L_YIELD;
|
||||
const DEAD_SYM: SymbolId = raw::L_WARNING;
|
||||
|
||||
|
@ -177,12 +247,6 @@ fn expandable_into_is_stitchable_with_target() {
|
|||
}
|
||||
}
|
||||
|
||||
// Asserts that the wrapping `StitchableParseState` has transformed the
|
||||
// `TestParseState` into something stitchable.
|
||||
//
|
||||
// This serves as a sanity check for the below.
|
||||
assert_impl_all!(ExpansionSut: StitchableParseState<TargetParseState>);
|
||||
|
||||
// The `ExpandableInto` trait alias is responsible for asserting that a
|
||||
// given parser is an expansion parser that is able to be converted
|
||||
// into a parser stitchable with the target.
|
||||
|
|
Loading…
Reference in New Issue