tamer: parse: More flexible Transition API

This does some cleanup and adds `parse::Object` for use in disambiguating
`From` for `ParseStatus`, allowing the `Transition` API to be much more
flexible in the data it accepts and automatically converts.  This allows us
to concisely provide raw output data to be wrapped, or provide `ParseStatus`
directly when more convenient.

There aren't yet examples in the docs; I'll do so once I make sure this API
is actually utilized as intended.

DEV-10863
main
Mike Gerwitz 2022-03-25 16:45:32 -04:00
parent c0fa89222e
commit f402e51d04
6 changed files with 75 additions and 37 deletions

View File

@ -19,7 +19,7 @@
use super::{SymAttrs, XmloError};
use crate::{
parse::{ParseState, Transition, TransitionResult},
parse::{self, ParseState, Transition, TransitionResult},
sym::{st::*, SymbolId},
xir::{attr::Attr, flat::Object as Xirf},
};
@ -87,6 +87,8 @@ pub enum XmloEvent {
Eoh,
}
impl parse::Object for XmloEvent {}
/// A [`Result`] with a hard-coded [`XmloError`] error type.
///
/// This is the result of every [`XmloReader`] operation that could
@ -126,7 +128,7 @@ impl ParseState for XmloReaderState {
(Ready, _) => Transition(Ready).err(XmloError::UnexpectedRoot),
(Package, Xirf::Attr(Attr(name, value, _))) => {
Transition(Package).with(match name {
Transition(Package).ok(match name {
QN_NAME => XmloEvent::PkgName(value),
QN_UUROOTPATH => XmloEvent::PkgRootPath(value),
QN_PROGRAM => XmloEvent::PkgProgramFlag,

View File

@ -54,6 +54,16 @@ impl<T: Token> From<T> for Span {
}
}
/// An IR object produced by a lowering operation on one or more [`Token`]s.
///
/// Note that an [`Object`] may also be a [`Token`] if it will be in turn
/// fed to another [`Parser`] for lowering.
///
/// This trait exists to disambiguate an otherwise unbounded type for
/// [`From`] conversions,
/// used in the [`Transition`] API to provide greater flexibility.
pub trait Object: Debug + PartialEq + Eq {}
/// An infallible [`Token`] stream.
///
/// If the token stream originates from an operation that could potentially
@ -96,7 +106,7 @@ pub trait ParseState: Default + PartialEq + Eq + Debug {
type Token: Token;
/// Objects produced by a parser utilizing these states.
type Object: Debug + PartialEq + Eq;
type Object: Object;
/// Errors specific to this set of states.
type Error: Debug + Error + PartialEq + Eq;
@ -150,10 +160,7 @@ pub trait ParseState: Default + PartialEq + Eq + Debug {
///
/// This is used by [`ParseState::parse_token`];
/// see that function for rationale.
pub type ParseStateResult<S> = Result<
ParseStatus<<S as ParseState>::Token, <S as ParseState>::Object>,
<S as ParseState>::Error,
>;
pub type ParseStateResult<S> = Result<ParseStatus<S>, <S as ParseState>::Error>;
/// A state transition with associated data.
///
@ -182,8 +189,31 @@ impl<S: ParseState> Transition<S> {
///
/// This allows [`ParseState::parse_token`] to emit a parsed object and
/// corresponds to [`ParseStatus::Object`].
pub fn with(self, obj: S::Object) -> TransitionResult<S> {
TransitionResult(self, Ok(ParseStatus::Object(obj)))
pub fn ok<T>(self, obj: T) -> TransitionResult<S>
where
T: Into<ParseStatus<S>>,
{
TransitionResult(self, Ok(obj.into()))
}
/// A transition with corresponding error.
///
/// This indicates a parsing failure.
/// The state ought to be suitable for error recovery.
pub fn err<E: Into<S::Error>>(self, err: E) -> TransitionResult<S> {
TransitionResult(self, Err(err.into()))
}
/// A state transition with corresponding [`Result`].
///
/// This translates the provided [`Result`] in a manner equivalent to
/// [`Transition::ok`] and [`Transition::err`].
pub fn result<T, E>(self, result: Result<T, E>) -> TransitionResult<S>
where
T: Into<ParseStatus<S>>,
E: Into<S::Error>,
{
TransitionResult(self, result.map(Into::into).map_err(Into::into))
}
/// A state transition indicating that more data is needed before an
@ -202,14 +232,6 @@ impl<S: ParseState> Transition<S> {
pub fn dead(self, tok: S::Token) -> TransitionResult<S> {
TransitionResult(self, Ok(ParseStatus::Dead(tok)))
}
/// A transition with corresponding error.
///
/// This indicates a parsing failure.
/// The state ought to be suitable for error recovery.
pub fn err<E: Into<S::Error>>(self, err: E) -> TransitionResult<S> {
TransitionResult(self, Err(err.into()))
}
}
impl<S: ParseState> Into<(Transition<S>, ParseStateResult<S>)>
@ -583,7 +605,7 @@ impl<S: ParseState, I: TokenStream<S::Token>> From<I> for Parser<S, I> {
/// Result of a parsing operation.
#[derive(Debug, PartialEq, Eq)]
pub enum ParseStatus<T, O> {
pub enum ParseStatus<S: ParseState> {
/// Additional tokens are needed to complete parsing of the next object.
Incomplete,
@ -591,7 +613,7 @@ pub enum ParseStatus<T, O> {
///
/// This does not indicate that the parser is complete,
/// as more objects may be able to be emitted.
Object(O),
Object(S::Object),
/// Parser encountered a dead state relative to the given token.
///
@ -616,7 +638,13 @@ pub enum ParseStatus<T, O> {
///
/// If there is no parent context to handle the token,
/// [`Parser`] must yield an error.
Dead(T),
Dead(S::Token),
}
impl<S: ParseState<Object = T>, T: Object> From<T> for ParseStatus<S> {
fn from(obj: T) -> Self {
Self::Object(obj)
}
}
/// Result of a parsing operation.
@ -636,8 +664,8 @@ pub enum Parsed<O> {
Object(O),
}
impl<T: Token, O> From<ParseStatus<T, O>> for Parsed<O> {
fn from(status: ParseStatus<T, O>) -> Self {
impl<S: ParseState> From<ParseStatus<S>> for Parsed<S::Object> {
fn from(status: ParseStatus<S>) -> Self {
match status {
ParseStatus::Incomplete => Parsed::Incomplete,
ParseStatus::Object(x) => Parsed::Object(x),
@ -677,6 +705,8 @@ pub mod test {
}
}
impl Object for TestToken {}
#[derive(Debug, PartialEq, Eq)]
enum EchoState {
Empty,
@ -696,7 +726,7 @@ pub mod test {
fn parse_token(self, tok: TestToken) -> TransitionResult<Self> {
match tok {
TestToken::Comment(..) => Transition(Self::Done).with(tok),
TestToken::Comment(..) => Transition(Self::Done).ok(tok),
TestToken::Close(..) => {
Transition(self).err(EchoStateError::InnerError(tok))
}

View File

@ -68,6 +68,8 @@ impl Token for Attr {
}
}
impl crate::parse::Object for Attr {}
impl Display for Attr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "`@{}=\"{}\"` at {}", self.0, self.1, self.2 .0)

View File

@ -58,7 +58,7 @@ impl ParseState for AttrParseState {
(Empty, invalid) => Transition(Empty).dead(invalid),
(Name(name, nspan), XirToken::AttrValue(value, vspan)) => {
Transition(Empty).with(Attr::new(name, value, (nspan, vspan)))
Transition(Empty).ok(Attr::new(name, value, (nspan, vspan)))
}
(Name(name, nspan), invalid) => {

View File

@ -44,7 +44,7 @@ use super::{
};
use crate::{
parse::{
ParseState, ParseStatus, ParsedResult, Token, Transition,
self, ParseState, ParseStatus, ParsedResult, Token, Transition,
TransitionResult,
},
span::Span,
@ -131,6 +131,8 @@ impl Token for Object {
}
}
impl parse::Object for Object {}
impl Display for Object {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Object::*;
@ -204,7 +206,7 @@ where
match (self, tok) {
// Comments are permitted before and after the first root element.
(st @ (PreRoot | Done), XirToken::Comment(sym, span)) => {
Transition(st).with(Object::Comment(sym, span))
Transition(st).ok(Object::Comment(sym, span))
}
(PreRoot, tok @ XirToken::Open(..)) => {
@ -224,7 +226,7 @@ where
}
(Transition(sa), Ok(Obj(attr))) => {
Transition(AttrExpected(stack, sa))
.with(Object::Attr(attr))
.ok(Object::Attr(attr))
}
(_, Ok(Dead(lookahead))) => {
Self::parse_node(stack, lookahead)
@ -280,7 +282,7 @@ where
stack.push((qname, span));
// Delegate to the attribute parser until it is complete.
Transition(AttrExpected(stack, SA::default())).with(Open(
Transition(AttrExpected(stack, SA::default())).ok(Open(
qname,
span,
Depth(depth),
@ -303,7 +305,7 @@ where
}
// Final closing tag (for root node) completes the document.
(..) if stack.len() == 0 => Transition(Done).with(Close(
(..) if stack.len() == 0 => Transition(Done).ok(Close(
close_oqname,
close_span,
Depth(0),
@ -312,7 +314,7 @@ where
(..) => {
let depth = stack.len();
Transition(NodeExpected(stack)).with(Close(
Transition(NodeExpected(stack)).ok(Close(
close_oqname,
close_span,
Depth(depth),
@ -322,16 +324,16 @@ where
}
XirToken::Comment(sym, span) => {
Transition(NodeExpected(stack)).with(Comment(sym, span))
Transition(NodeExpected(stack)).ok(Comment(sym, span))
}
XirToken::Text(sym, span) => {
Transition(NodeExpected(stack)).with(Text(sym, span))
Transition(NodeExpected(stack)).ok(Text(sym, span))
}
XirToken::CData(sym, span) => {
Transition(NodeExpected(stack)).with(CData(sym, span))
Transition(NodeExpected(stack)).ok(CData(sym, span))
}
XirToken::Whitespace(ws, span) => {
Transition(NodeExpected(stack)).with(Whitespace(ws, span))
Transition(NodeExpected(stack)).ok(Whitespace(ws, span))
}
// We should transition to `State::Attr` before encountering any

View File

@ -180,7 +180,7 @@ use super::{
use crate::{
parse::{
ParseError, ParseResult, ParseState, ParseStatus, ParsedResult,
self, ParseError, ParseResult, ParseState, ParseStatus, ParsedResult,
Transition, TransitionResult,
},
span::Span,
@ -282,6 +282,8 @@ impl Tree {
}
}
impl parse::Object for Tree {}
/// Element node.
///
/// This represents an [XML element] beginning with an opening tag that is
@ -564,7 +566,7 @@ impl<SA: StackAttrParseState> ParseState for Stack<SA> {
.map(ElementStack::consume_child_or_complete)
.map(|new_stack| match new_stack {
Stack::ClosedElement(ele) => {
Transition(Empty).with(Tree::Element(ele))
Transition(Empty).ok(Tree::Element(ele))
}
_ => Transition(new_stack).incomplete(),
})
@ -577,7 +579,7 @@ impl<SA: StackAttrParseState> ParseState for Stack<SA> {
.map(ElementStack::consume_child_or_complete)
.map(|new_stack| match new_stack {
Stack::ClosedElement(ele) => {
Transition(Empty).with(Tree::Element(ele))
Transition(Empty).ok(Tree::Element(ele))
}
_ => Transition(new_stack).incomplete(),
})