gcode/
diags.rs

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
//! Diagnostics for alloc-based parsing; returned by [`parse`](crate::parse) when errors occur.
//!
//! The parser reports recoverable issues via the [`Diagnostics`](crate::core::Diagnostics) trait;
//! this module provides a concrete implementation that collects them.
#![allow(missing_docs)]

use alloc::{string::String, string::ToString, vec::Vec};
use core::fmt::{self, Display, Formatter};

use crate::core::{Span, TokenType};

/// A single recoverable parse issue with a [`DiagnosticKind`] and source [`Span`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Diagnostic {
    pub kind: DiagnosticKind,
    pub span: Span,
}

impl Display for Diagnostic {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let Diagnostic {
            kind,
            span: Span { line, .. },
        } = self;
        let line = line + 1;

        write!(f, "{kind} on line {line}")
    }
}

#[cfg(feature = "defmt")]
impl defmt::Format for Diagnostic {
    fn format(&self, fmt: defmt::Formatter<'_>) {
        let Diagnostic {
            kind,
            span: Span { line, .. },
        } = self;
        let line = line + 1;

        defmt::write!(fmt, "{} on line {}", kind, line)
    }
}

/// Category of parse diagnostic emitted during recovery.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum DiagnosticKind {
    /// text the parser could not interpret (e.g. invalid token).
    UnknownContent { text: String },
    /// the parser expected one of `expected` token types but found `actual`.
    Unexpected {
        actual: String,
        expected: Vec<TokenType>,
    },
}

impl Display for DiagnosticKind {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            DiagnosticKind::UnknownContent { text } => {
                write!(f, "Unknown content: {}", text)
            },
            DiagnosticKind::Unexpected { actual, expected } => {
                let expected = expected
                    .iter()
                    .map(|s| s.as_str())
                    .collect::<Vec<_>>()
                    .join(", ");
                write!(f, "Unexpected: {actual} (expected: {expected})")
            },
        }
    }
}

#[cfg(feature = "defmt")]
impl defmt::Format for DiagnosticKind {
    fn format(&self, fmt: defmt::Formatter<'_>) {
        match self {
            DiagnosticKind::UnknownContent { text } => {
                defmt::write!(fmt, "Unknown content: {}", text)
            },
            DiagnosticKind::Unexpected { actual, expected } => {
                let expected = expected
                    .iter()
                    .map(|s| s.as_str())
                    .collect::<Vec<_>>()
                    .join(", ");
                defmt::write!(
                    fmt,
                    "Unexpected: {} (expected: {})",
                    actual,
                    expected
                )
            },
        }
    }
}

/// Collection of [`Diagnostic`]s produced by a parse.
///
/// Returned by [`parse`](crate::parse) in `Err` when any diagnostic was emitted.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Diagnostics(Vec<Diagnostic>);

impl Diagnostics {
    /// Creates an empty collection.
    pub const fn new() -> Self {
        Self(Vec::new())
    }

    /// Returns true if no diagnostics were collected.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Consumes self and returns the inner vector of diagnostics.
    pub fn into_inner(self) -> Vec<Diagnostic> {
        self.0
    }

    /// Iterates over the collected diagnostics.
    pub fn iter(&self) -> impl Iterator<Item = &Diagnostic> {
        self.0.iter()
    }
}

impl Default for Diagnostics {
    fn default() -> Self {
        Self::new()
    }
}

impl crate::core::Diagnostics for Diagnostics {
    fn emit_unknown_content(&mut self, text: &str, span: Span) {
        self.0.push(Diagnostic {
            kind: DiagnosticKind::UnknownContent {
                text: text.to_string(),
            },
            span,
        });
    }

    fn emit_unexpected(
        &mut self,
        actual: &str,
        expected: &[TokenType],
        span: Span,
    ) {
        self.0.push(Diagnostic {
            kind: DiagnosticKind::Unexpected {
                actual: actual.to_string(),
                expected: expected.to_vec(),
            },
            span,
        });
    }
}