// 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 .
//! 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 {
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 {
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());
}
}