1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
//! Recoverable parser helper module. Contains trait and types that are using
//! during implementation of parsing with recovery after semantic errors.
//!
//! Recoverable parser is a type of parsing technique when parser don't give up
//! after getting invalid token, and instead continue to parse code to provide
//! more info about [`TokenStream`] to IDE or user.
//!
//! Instead of failing after first unclosed tag, or invalid block, recoverable
//! parser will try to check if there any other syntax or semantic errors.
//!
//! Example:
//! ```rust
//!   # use quote::quote;
//!   # use rstml::{Parser, ParserConfig};
//!   # Parser::new(ParserConfig::default()).parse_recoverable(quote! {
//!   <div hello={world.} /> // dot after world is invalid syn::Expr
//!   <>
//!       <div>"1"</x> // incorrect closed tag
//!       <div>"2"</div>
//!       <div>"3"</div>
//!       <div {"some-attribute-from-rust-block"}/>
//!   </>
//!   <bar> // unclosed tag
//!   # });
//! ```
//! If this example was parsed by regular parser, it will fail with "invalid
//! expression error" and no output. User will see only one error, and IDE
//! cannot produce any completion in case of invalid expression.
//!
//! But recoverable parser differ (see [`Parser::parse_recoverable`]), it will
//! return array of errors and array of [`Node`]. Errors should be emitted, and
//! value should be handled as no errors was found. In result, user will see all
//! errors, and IDE can provide completion even if some part of token stream was
//! unexpected.
//!
//!
//! [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
//! [`Parser::parse_recoverable`]: struct.Parser.html#method.parse_recoverable
//! [`Node`]: struct.Node.html

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,
};

/// Config of parser.
/// Used to extend parsing functionality by user needs.
///
/// Can't be created directly, instead use [`From<ParserConfig>::from`].
#[derive(Default, Clone)]
pub struct RecoveryConfig {
    ///
    /// Try to parse invalid syn::Block as something.
    /// Usefull to make expressions more IDE-friendly.
    pub(crate) recover_block: bool,
    /// elements that has no child and is always self closed like <img> and <br>
    pub(crate) always_self_closed_elements: HashSet<&'static str>,
    /// Elements like `<script>` `<style>`, context of which is not a valid
    /// html, and should be provided as is.
    pub(crate) raw_text_elements: HashSet<&'static str>,
    pub(crate) transform_block: Option<Rc<TransformBlockFn>>,
    /// Allows wildcard closing tag matching for blocks
    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()
    }
}

/// Context that is provided in [`ParseRecoverable`] interface.
/// Used to save [`Diagnostic`] messages or [`syn::Result`].
///
/// Also can be extended with user needs through [`RecoveryConfig`].
#[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)
    }

    /// Parse token using [`syn::parse::Parse`]
    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
            }
        }
    }
    /// Parse token using closure
    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
            }
        }
    }

    /// Parse token using [`ParseRecoverable`]
    pub fn parse_recoverable<T: ParseRecoverable>(&mut self, input: ParseStream) -> Option<T> {
        T::parse_recoverable(self, input)
    }

    /// Save diagnostic message of [`syn::Result`]
    /// and convert result to `Option`, that can be used directly
    /// as output in [`ParseRecoverable::parse_recoverable`]
    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
            }
        }
    }

    /// Push custom message of [`syn::Error`] or
    /// [`proc_macro2_diagnostics::Diagnostic`]
    pub fn push_diagnostic(&mut self, diagnostic: impl Into<Diagnostic>) {
        let diag = diagnostic.into();
        // println!(
        //     "Push diagnostic: {:?}, backtrace={}",
        //     diag,
        //     std::backtrace::Backtrace::capture()
        // );
        self.diagnostics.push(diag);
    }
}

///
/// Result of parsing.
#[derive(Debug)]
pub enum ParsingResult<T> {
    /// Fully valid ast that was parsed without errors.
    Ok(T),
    /// The ast contain invalid starting tokens, and cannot be parsed.
    Failed(Vec<Diagnostic>),
    /// The ast can be partially parsed,
    /// but some tokens was skipped during parsing, or their meaning was
    /// changed.
    Partial(T, Vec<Diagnostic>),
}

impl<T> ParsingResult<T> {
    /// Create new ParsingResult from optional value and accumulated errors.
    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),
        }
    }

    ///
    /// Convert into [`syn::Result`], with fail on first diagnostic message,
    /// Ignores any diagnostic non error message when result is available.
    /// Returns Error on [`ParsingResult::Failed`], and
    /// [`ParsingResult::Partial`].
    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> {
    ///
    /// Convert into syn::Result,
    /// Returns Error on ParsingResult::Failed, and ParsingResult::Partial.
    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(),
        }
    }
}

///
/// Adaptor to provide a [`syn::parse::Parse`] interface to [`ParseRecoverable`]
/// types. Returns error if any error was set in [`RecoverableContext`] during
/// parsing. Use Default implementation of [`RecoveryConfig`].
///
/// Panics:
/// If [`ParseRecoverable`] implementation doesn't save any diagnostic message,
/// and return [`None`].
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)
    }
}

///
/// Parsing interface for recoverable [`TokenStream`] parsing,
///     analog to [`syn::parse::Parse`] but with ability to skip unexpected
/// tokens, and more diagnostic messages.
///
/// - If input stream can be parsed to valid, or partially valid object
/// [`Option::Some`] should be returned.
///
/// - If object is parsed partially one can save
/// diagnostic message in [`RecoverableContext`].
///
/// - If object is failed to parse
/// [`Option::None`] should be returned, and any message should be left in
/// [`RecoverableContext`].
///
/// Instead of using [`RecoverableContext`] the interface can be changed to the
/// following:
/// ```rust
/// # use syn::parse::ParseStream;
/// # use rstml::ParsingResult;
/// pub trait ParseRecoverable: Sized {
///     fn parse_recoverable(input: ParseStream) -> ParsingResult<Self>;
/// }
/// ```
/// It would more type-safe, but because [`std::ops::Try`] is not stable,
/// writing implementation for this trait would end with a lot of boilerplate.
///
/// [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
pub trait ParseRecoverable: Sized {
    fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self>;
}