use std::{collections::HashSet, fmt::Debug, rc::Rc};
use proc_macro2_diagnostics::{Diagnostic, Level};
use syn::parse::{Parse, ParseStream};
use crate::{
config::{ElementWildcardFn, TransformBlockFn},
node::CustomNode,
ParserConfig,
};
#[derive(Default, Clone)]
pub struct RecoveryConfig {
pub(crate) recover_block: bool,
pub(crate) always_self_closed_elements: HashSet<&'static str>,
pub(crate) raw_text_elements: HashSet<&'static str>,
pub(crate) transform_block: Option<Rc<TransformBlockFn>>,
pub(crate) element_close_wildcard: Option<Rc<ElementWildcardFn>>,
}
impl PartialEq for RecoveryConfig {
fn eq(&self, other: &Self) -> bool {
self.recover_block == other.recover_block
&& self.always_self_closed_elements == other.always_self_closed_elements
&& self.raw_text_elements == other.raw_text_elements
&& self.transform_block.is_some() == other.transform_block.is_some()
&& self.element_close_wildcard.is_some() == other.element_close_wildcard.is_some()
}
}
impl Eq for RecoveryConfig {}
impl Debug for RecoveryConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RecoveryConfig")
.field("recover_block", &self.recover_block)
.field(
"always_self_closed_elements",
&self.always_self_closed_elements,
)
.field("raw_text_elements", &self.raw_text_elements)
.field(
"element_close_wildcard",
&self.element_close_wildcard.is_some(),
)
.finish()
}
}
#[derive(Debug, Default, Clone)]
pub struct RecoverableContext {
pub(super) diagnostics: Vec<Diagnostic>,
config: RecoveryConfig,
}
impl PartialEq for RecoverableContext {
fn eq(&self, other: &Self) -> bool {
if self.diagnostics.len() != other.diagnostics.len() || self.config != other.config {
return false;
}
self.diagnostics
.iter()
.zip(other.diagnostics.iter())
.all(|(a, b)| format!("{:?}", a) == format!("{:?}", b))
}
}
impl Eq for RecoverableContext {}
impl RecoverableContext {
pub fn new(config: RecoveryConfig) -> Self {
Self {
diagnostics: vec![],
config,
}
}
pub fn config(&self) -> &RecoveryConfig {
&self.config
}
pub fn parse_result<T>(self, val: Option<T>) -> ParsingResult<T> {
ParsingResult::from_parts(val, self.diagnostics)
}
pub fn parse_simple<T: Parse>(&mut self, input: ParseStream) -> Option<T> {
match input.parse() {
Ok(v) => Some(v),
Err(e) => {
self.push_diagnostic(e);
None
}
}
}
pub fn parse_mixed_fn<F, T>(&mut self, input: ParseStream, mut parser: F) -> Option<T>
where
F: FnMut(&mut Self, ParseStream) -> Result<T, syn::Error>,
{
match parser(self, input) {
Ok(v) => Some(v),
Err(e) => {
self.push_diagnostic(e);
None
}
}
}
pub fn parse_recoverable<T: ParseRecoverable>(&mut self, input: ParseStream) -> Option<T> {
T::parse_recoverable(self, input)
}
pub fn save_diagnostics<T>(&mut self, val: syn::Result<T>) -> Option<T> {
match val {
Ok(v) => Some(v),
Err(e) => {
self.push_diagnostic(e);
None
}
}
}
pub fn push_diagnostic(&mut self, diagnostic: impl Into<Diagnostic>) {
let diag = diagnostic.into();
self.diagnostics.push(diag);
}
}
#[derive(Debug)]
pub enum ParsingResult<T> {
Ok(T),
Failed(Vec<Diagnostic>),
Partial(T, Vec<Diagnostic>),
}
impl<T> ParsingResult<T> {
pub fn from_parts(value: Option<T>, errors: Vec<Diagnostic>) -> Self {
match (value, errors) {
(Some(v), err) if err.is_empty() => Self::Ok(v),
(Some(v), err) => Self::Partial(v, err),
(None, err) => Self::Failed(err),
}
}
pub fn into_result(self) -> syn::Result<T> {
match self {
ParsingResult::Ok(r) => Ok(r),
ParsingResult::Failed(errors) => Err(errors
.into_iter()
.next()
.unwrap_or_else(|| {
Diagnostic::new(
Level::Error,
"Object parsing failed, but no additional info was provided",
)
})
.into()),
ParsingResult::Partial(ok, errors) => {
if let Some(err) = errors
.into_iter()
.filter(|p| p.level() == Level::Error)
.next()
{
Err(err.into())
} else {
Ok(ok)
}
}
}
}
pub fn split(self) -> (Option<T>, Vec<Diagnostic>) {
match self {
Self::Ok(r) => (Some(r), vec![]),
Self::Failed(errors) => (None, errors),
Self::Partial(r, errors) => (Some(r), errors),
}
}
pub fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
*self = match std::mem::replace(self, ParsingResult::Failed(vec![])) {
Self::Ok(r) => Self::Partial(r, vec![diagnostic]),
Self::Failed(errors) => {
Self::Failed(errors.into_iter().chain(Some(diagnostic)).collect())
}
Self::Partial(r, errors) => {
Self::Partial(r, errors.into_iter().chain(Some(diagnostic)).collect())
}
};
}
pub fn is_ok(&self) -> bool {
matches!(self, Self::Ok(_))
}
}
impl<T> ParsingResult<Vec<T>> {
pub fn split_vec(self) -> (Vec<T>, Vec<Diagnostic>) {
let (r, e) = self.split();
(r.unwrap_or_default(), e)
}
pub fn from_parts_vec(value: Vec<T>, errors: Vec<Diagnostic>) -> Self {
match (value, errors) {
(v, err) if err.is_empty() => Self::Ok(v),
(v, err) if !v.is_empty() => Self::Partial(v, err),
(_, err) => Self::Failed(err),
}
}
}
impl<T> From<syn::Result<T>> for ParsingResult<T> {
fn from(result: syn::Result<T>) -> ParsingResult<T> {
match result {
Result::Ok(r) => ParsingResult::Ok(r),
Result::Err(e) => ParsingResult::Failed(vec![e.into()]),
}
}
}
impl<C: CustomNode> From<ParserConfig<C>> for RecoveryConfig {
fn from(config: ParserConfig<C>) -> Self {
RecoveryConfig {
recover_block: config.recover_block,
raw_text_elements: config.raw_text_elements.clone(),
always_self_closed_elements: config.always_self_closed_elements.clone(),
transform_block: config.transform_block.clone(),
element_close_wildcard: config.element_close_wildcard.clone(),
}
}
}
pub struct Recoverable<T>(pub T);
impl<T> Recoverable<T> {
pub fn inner(self) -> T {
self.0
}
}
impl<T: ParseRecoverable> Parse for Recoverable<T> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut empty_context = RecoverableContext::default();
let parse = T::parse_recoverable(&mut empty_context, input);
empty_context
.parse_result(parse)
.into_result()
.map(Recoverable)
}
}
pub trait ParseRecoverable: Sized {
fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self>;
}