tamer: parse::parser::finalize: Introduce FinalizedParser

This newtype allows a caller to prove (using types) that a parser of a given
type (`ParseState`) has been finalized.

This will be used by the lowering pipeline to ensure that all parsers in the
pipeline end up getting finalized (as you can see from a TODO added in the
code, one of them is missing).  The lack of such a type was an oversight
during the (rather stressed) development of the parsing system, and I
shouldn't need to resort to unit tests to verify that parsers have been
finalized.

DEV-13158
main
Mike Gerwitz 2022-10-26 09:37:25 -04:00
parent 7e62276907
commit 2087672c47
5 changed files with 45 additions and 19 deletions

View File

@ -213,7 +213,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
let asg = sut.finalize().unwrap();
let asg = sut.finalize().unwrap().into_context();
let ident_node =
asg.lookup(sym).expect("identifier was not added to graph");
@ -254,7 +254,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
let asg = sut.finalize().unwrap();
let asg = sut.finalize().unwrap().into_context();
let ident_node =
asg.lookup(sym).expect("identifier was not added to graph");
@ -292,7 +292,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
let asg = sut.finalize().unwrap();
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup(ident)
@ -325,7 +325,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentDecl
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentFragment
let asg = sut.finalize().unwrap();
let asg = sut.finalize().unwrap().into_context();
let ident_node =
asg.lookup(sym).expect("identifier was not added to graph");
@ -362,7 +362,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
let asg = sut.finalize().unwrap();
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup(sym)
@ -401,7 +401,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentDecl
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentRoot
let asg = sut.finalize().unwrap();
let asg = sut.finalize().unwrap().into_context();
let ident_node = asg
.lookup(sym)

View File

@ -423,7 +423,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // PkgRootPath
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // Eoh
let ctx = sut.finalize().unwrap();
let ctx = sut.finalize().unwrap().into_context();
assert_eq!(Some(name), ctx.prog_name);
assert_eq!(Some(relroot), ctx.relroot);
@ -510,7 +510,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // SymDecl (@src)
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // Eoh
let ctx = sut.finalize().unwrap();
let ctx = sut.finalize().unwrap().into_context();
let mut founds = ctx.found.unwrap().into_iter().collect::<Vec<_>>();
// Just to remove nondeterminism in case the iteration order happens
@ -621,7 +621,7 @@ mod test {
);
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // Eoh
let ctx = sut.finalize().unwrap();
let ctx = sut.finalize().unwrap().into_context();
// Both above symbols were local (no `src`),
// but note that we don't care if it's None or initialized with a

View File

@ -29,7 +29,7 @@ mod trace;
pub use error::ParseError;
pub use lower::{Lower, LowerIter, ParsedObject};
pub use parser::{Parsed, ParsedResult, Parser};
pub use parser::{FinalizedParser, Parsed, ParsedResult, Parser};
pub use state::{
context::{Context, Empty as EmptyContext, NoContext},
ClosedParseState, ParseResult, ParseState, ParseStatus, Transition,
@ -376,7 +376,7 @@ pub mod test {
let mut toks = vec![TestToken::MarkDone(DS)].into_iter();
let mut sut = Sut::from(&mut toks);
sut.next().unwrap().unwrap();
let ctx = sut.finalize().unwrap();
let ctx = sut.finalize().unwrap().into_context();
assert_eq!(ctx, Default::default());
// Next, verify that the context that is manipulated is the context
@ -385,7 +385,7 @@ pub mod test {
let mut toks = vec![TestToken::SetCtxVal(5)].into_iter();
let mut sut = Sut::from(&mut toks);
sut.next().unwrap().unwrap();
let ctx = sut.finalize().unwrap();
let ctx = sut.finalize().unwrap().into_context();
assert_eq!(ctx, StubContext { val });
// Finally, verify that the context provided is the context that is
@ -395,7 +395,7 @@ pub mod test {
let mut toks = vec![TestToken::MarkDone(DS)].into_iter();
let mut sut = EchoState::parse_with_context(&mut toks, given_ctx);
sut.next().unwrap().unwrap();
let ctx = sut.finalize().unwrap();
let ctx = sut.finalize().unwrap().into_context();
assert_eq!(ctx, StubContext { val });
}

View File

@ -20,8 +20,8 @@
//! IR lowering operation between [`Parser`]s.
use super::{
state::ClosedParseState, NoContext, Object, ParseError, ParseState, Parsed,
Parser, Token, TransitionResult, UnknownToken,
state::ClosedParseState, FinalizedParser, NoContext, Object, ParseError,
ParseState, Parsed, Parser, Token, TransitionResult, UnknownToken,
};
use crate::diagnose::Diagnostic;
use std::{fmt::Display, iter, marker::PhantomData};
@ -62,7 +62,7 @@ where
{
/// Consume inner parser and yield its context.
#[inline]
fn finalize(self) -> Result<LS::Context, E> {
fn finalize(self) -> Result<FinalizedParser<LS>, E> {
self.lower.finalize().map_err(|(_, e)| e.into())
}
}
@ -129,6 +129,8 @@ where
_phantom: PhantomData::default(),
};
f(&mut iter)
// TODO: Finalize!
}
/// Perform a lowering operation between two parsers where the context
@ -155,7 +157,10 @@ where
};
let val = f(&mut iter)?;
iter.finalize().map(|ctx| (val, ctx))
// TODO: Further propagate `FinalizedParser`
iter.finalize()
.map(FinalizedParser::into_context)
.map(|ctx| (val, ctx))
}
}

View File

@ -178,9 +178,10 @@ impl<S: ClosedParseState, I: TokenStream<S::Token>> Parser<S, I> {
/// is a decision made by the [`ParseState`].
pub fn finalize(
self,
) -> Result<S::Context, (Self, ParseError<S::Token, S::Error>)> {
) -> Result<FinalizedParser<S>, (Self, ParseError<S::Token, S::Error>)>
{
match self.assert_accepting() {
Ok(()) => Ok(self.ctx),
Ok(()) => Ok(FinalizedParser(self.ctx)),
Err(err) => Err((self, err)),
}
}
@ -454,6 +455,26 @@ where
}
}
/// Residual state of a parser that has been finalized with
/// [`Parser::finalize`].
///
/// This type can be used to ensure that parsers are always finalized at the
/// end of an operation by providing such evidence to a caller.
///
/// If the inner [`ParseState::Context`] is empty or no longer needed,
/// then this can be safely dropped without use.
#[derive(Debug, PartialEq)]
pub struct FinalizedParser<S: ParseState>(S::Context);
impl<S: ParseState> FinalizedParser<S> {
/// Take ownership over the inner [`ParseState::Context`].
pub fn into_context(self) -> S::Context {
match self {
Self(ctx) => ctx,
}
}
}
#[cfg(test)]
pub mod test {
use super::*;