tame/tamer/src/xir/attr/parse.rs

240 lines
7.0 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

// XIRT attribute parsers
//
// Copyright (C) 2014-2022 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Parse XIR attribute [`TokenStream`][super::super::TokenStream]s.
use crate::{
diagnose::{Annotate, AnnotatedSpan, Diagnostic},
parse::{NoContext, ParseState, Token, Transition, TransitionResult},
span::Span,
xir::{QName, Token as XirToken},
};
use std::{error::Error, fmt::Display};
use super::Attr;
/// Attribute parser DFA.
///
/// While this parser does store the most recently encountered [`QName`]
/// and [`Span`],
/// these data are used only for emitting data about the accepted state;
/// they do not influence the automaton's state transitions.
/// The actual parsing operation is therefore a FSM,
/// not a PDA.
#[derive(Debug, Eq, PartialEq)]
pub enum AttrParseState {
Empty,
Name(QName, Span),
}
impl ParseState for AttrParseState {
type Token = XirToken;
type Object = Attr;
type Error = AttrParseError;
fn parse_token(
self,
tok: Self::Token,
_: NoContext,
) -> TransitionResult<Self> {
use AttrParseState::{Empty, Name};
match (self, tok) {
(Empty, XirToken::AttrName(name, span)) => {
Transition(Name(name, span)).incomplete()
}
(Empty, invalid) => Transition(Empty).dead(invalid),
(Name(name, nspan), XirToken::AttrValue(value, vspan)) => {
Transition(Empty).ok(Attr::new(name, value, (nspan, vspan)))
}
(Name(name, nspan), invalid) => {
// Restore state for error recovery.
Transition(Name(name, nspan)).err(
AttrParseError::AttrValueExpected(name, nspan, invalid),
)
}
}
}
#[inline]
fn is_accepting(&self) -> bool {
*self == Self::Empty
}
}
impl Default for AttrParseState {
fn default() -> Self {
Self::Empty
}
}
impl Display for AttrParseState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use AttrParseState::*;
match self {
Empty => write!(f, "expecting an attribute"),
Name(name, _) => {
write!(f, "expecting an attribute value for {name}")
}
}
}
}
/// Attribute parsing error.
#[derive(Debug, PartialEq, Eq)]
pub enum AttrParseError {
/// [`XirToken::AttrName`] was expected.
AttrNameExpected(XirToken),
/// [`XirToken::AttrValue`] was expected.
AttrValueExpected(QName, Span, XirToken),
}
impl Display for AttrParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AttrNameExpected(_) => {
write!(f, "attribute name expected")
}
Self::AttrValueExpected(name, _span, _tok) => {
write!(f, "expected value for `@{name}`",)
}
}
}
}
impl Error for AttrParseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
impl Diagnostic for AttrParseError {
fn describe(&self) -> Vec<AnnotatedSpan> {
match self {
Self::AttrNameExpected(tok) => tok.span().mark_error().into(),
Self::AttrValueExpected(_name, span, _tok) => {
span.mark_error().into()
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
convert::ExpectInto,
parse::{EmptyContext, ParseStatus, Parsed},
sym::GlobalSymbolIntern,
};
const S: Span = crate::span::DUMMY_SPAN;
const S2: Span = S.offset_add(1).unwrap();
#[test]
fn dead_if_first_token_is_non_attr() {
let tok = XirToken::Open("foo".unwrap_into(), S);
let sut = AttrParseState::default();
// There is no state that we can transition to,
// and we're in an empty accepting state.
assert_eq!(
(
// Make sure we're in the same state we started in so that
// we know we can accommodate recovery token(s).
Transition(AttrParseState::default()),
Ok(ParseStatus::Dead(tok.clone()))
),
sut.parse_token(tok, &mut EmptyContext).into()
);
}
#[test]
fn parse_single_attr() {
let attr = "attr".unwrap_into();
let val = "val".intern();
let toks = [XirToken::AttrName(attr, S), XirToken::AttrValue(val, S2)]
.into_iter();
let sut = AttrParseState::parse(toks);
assert_eq!(
Ok(vec![
Parsed::Incomplete,
Parsed::Object(Attr::new(attr, val, (S, S2))),
]),
sut.collect()
);
}
#[test]
fn parse_fails_when_attribute_value_missing_but_can_recover() {
let attr = "bad".unwrap_into();
let sut = AttrParseState::default();
// This token indicates that we're expecting a value to come next in
// the token stream.
let TransitionResult(Transition(sut), result) =
sut.parse_token(XirToken::AttrName(attr, S), &mut EmptyContext);
assert_eq!(result, Ok(ParseStatus::Incomplete));
// But we provide something else unexpected.
let TransitionResult(Transition(sut), result) =
sut.parse_token(XirToken::Close(None, S2), &mut EmptyContext);
assert_eq!(
result,
Err(AttrParseError::AttrValueExpected(
attr,
S,
XirToken::Close(None, S2)
))
);
// We should not be in an accepting state,
// given that we haven't finished parsing the attribute.
assert!(!sut.is_accepting());
// Despite this error,
// we should remain in a state that permits recovery should a
// proper token be substituted.
// Rather than checking for that state,
// let's actually attempt a recovery.
let recover = "value".intern();
let TransitionResult(Transition(sut), result) = sut
.parse_token(XirToken::AttrValue(recover, S2), &mut EmptyContext);
assert_eq!(
result,
Ok(ParseStatus::Object(Attr::new(attr, recover, (S, S2)))),
);
// Finally, we should now be in an accepting state.
assert!(sut.is_accepting());
}
}