nom_reprap_response/
lib.rs

1#![type_length_limit="1073741824"]
2
3#[macro_use] extern crate log;
4
5use nom::{
6    IResult,
7    character::streaming::*,
8    bytes::streaming::*,
9};
10use nom::branch::*;
11use nom::combinator::*;
12use nom::sequence::*;
13use nom::multi::*;
14
15mod delete_file;
16pub use delete_file::delete_file_resp;
17
18mod echo;
19pub use echo::echo;
20
21mod feedback;
22pub use feedback::*;
23
24mod file_list;
25pub use file_list::file_list;
26
27mod firmware_info_resp;
28pub use firmware_info_resp::firmware_info_resp;
29
30pub mod sd_responses;
31
32#[cfg(test)]
33mod tests;
34
35#[derive(Clone, Debug, PartialEq)]
36pub enum Response {
37    Greeting,
38    Ok(Option<Feedback>),
39    Feedback(Feedback),
40    Debug(String),
41    Echo(String),
42    Error(String),
43    Warning(String),
44    Resend(Resend),
45    Capability(String, bool),
46    FirmwareVersion(String),
47    Unknown,
48}
49
50#[derive(Clone, Debug, PartialEq)]
51pub struct Resend {
52    pub line_number: u32,
53}
54
55pub fn parse_response<'r>(src: &'r str) -> IResult<&'r str, (String, Response)> {
56    let mut parser = preceded(
57        // Quickly verify that the string contains a new line
58        peek(pair(
59            opt(not_line_ending),
60            line_ending,
61        )),
62        // Parse the response
63        response(),
64    );
65
66    let (remaining, response) = parser(src)?;
67
68    let matched_len = src.len() - remaining.len();
69    let matched_string = src[..matched_len].to_string();
70
71    Ok((remaining, (matched_string, response)))
72}
73
74pub fn response<'r>() -> impl FnMut(&'r str) -> IResult<&'r str, Response> {
75    terminated(
76        alt((
77            greeting,
78            debug,
79            echo,
80            ok_resp,
81            err_resp,
82            resend,
83            feedback_resp,
84            firmware_info_resp,
85            sd_responses::done_print_resp,
86            sd_responses::done_sd_write_resp,
87            sd_responses::file_deleted_resp,
88            file_list,
89            delete_file_resp,
90            firmware_version,
91            capability,
92            unknown_resp
93        )),
94        pair(space0, line_ending),
95    )
96}
97
98pub fn greeting<'r>(input: &'r str) ->  IResult<&'r str, Response> {
99    value(
100        Response::Greeting,
101        alt((
102            tag_no_case("start"),
103            tag_no_case("grbl"),
104            terminated(
105                tag_no_case("marlin "),
106                not_line_ending,
107            ),
108        )),
109    )(input)
110}
111
112pub fn debug<'r>(input: &'r str) ->  IResult<&'r str, Response> {
113    map(
114        alt((
115            // Klipper
116            preceded(
117                tag("// "),
118                not_line_ending,
119            ),
120            // Marlin
121            preceded(
122                pair(
123                    alt((
124                        tag_no_case("debug_"),
125                        peek(tag_no_case("compiled:")),
126                    )),
127                    space0,
128                ),
129                not_line_ending,
130            ),
131            tag_no_case("Init power off infomation."),
132            // "File(bin) deleted.\n"
133            // OR
134            // "File deleted.\n"
135            recognize(tuple((
136                tag_no_case("File"),
137                opt(tuple((
138                    char('('),
139                    many0(none_of(")\n\r")),
140                    char(')'),
141                ))),
142                space0,
143                tag_no_case("deleted."),
144            ))),
145            // "Deletion(bin) failed.\n"
146            recognize(tuple((
147                tag_no_case("Deletion"),
148                opt(tuple((
149                    char('('),
150                    many0(none_of(")\n\r")),
151                    char(')'),
152                ))),
153                space0,
154                tag_no_case("failed."),
155            ))),
156            // size: \n
157            // 591\n
158            recognize(tuple((
159                tag_no_case("size:"),
160                space0,
161                line_ending,
162                digit1,
163            ))),
164        )),
165        |s: &str| Response::Debug(s.to_string()),
166    )(input)
167}
168
169pub fn ok_resp<'r>(input: &'r str) ->  IResult<&'r str, Response> {
170    map(
171        preceded(
172            // Matches "ok" with up to 2 dropped preceeding "ok\n" characters (2 of \n, o or k):
173            // ok, okok, okokok, kok, ook
174            alt((
175                recognize(many_m_n(1, 3, tag_no_case("ok"))),
176                tag_no_case("kok"),
177                tag_no_case("ook"),
178            )),
179            opt(preceded(
180                space1,
181                alt((
182                    map(feedback, |feedback| Some(feedback)),
183                    map(not_line_ending, |unrecognized: &str| {
184                        if unrecognized.len() > 0 {
185                            warn!("Unrecognized feedback for response: \"ok {}\"", unrecognized);
186                        }
187
188                        None
189                    }),
190                )),
191            ),
192        )),
193        |feedback| Response::Ok(feedback.flatten())
194    )(input)
195}
196
197pub fn err_resp<'r>(input: &'r str) ->  IResult<&'r str, Response> {
198    map(
199        alt((
200            preceded(
201                pair(
202                    // Marlin
203                    tag_no_case("error:"),
204                    space0,
205                ),
206                alt((
207                    // Parses Marlin errors that are preceeded by an error number
208                    // eg. "Error:0\n:Description"
209                    recognize(tuple((
210                        digit1,
211                        newline,
212                        not_line_ending,
213                    ))),
214                    // Parses Marlin errors without an error number
215                    // eg. "Error: Description"
216                    recognize(tuple((
217                        not(digit1),
218                        not_line_ending,
219                    ))),
220                ))
221            ),
222            preceded(
223                pair(
224                    // Klipper
225                    tag("!!"),
226                    space0,
227                ),
228                not_line_ending,
229            ),
230        )),
231        |s: &str| {
232            if s == "checksum mismatch" {
233                Response::Warning(s.to_string())
234            } else {
235                Response::Error(s.to_string())
236            }
237        },
238    )(input)
239}
240
241pub fn resend<'r>(input: &'r str) ->  IResult<&'r str, Response> {
242    map(
243        delimited(
244            tuple((
245                alt((
246                    tag_no_case("resend"),
247                    tag_no_case("rs"),
248                )),
249                opt(char(':')),
250                space0,
251            )),
252            u32_str(),
253            space0,
254        ),
255        |line_number| Response::Resend(Resend { line_number }),
256    )(input)
257}
258
259// Response to M115
260pub fn firmware_version<'r>(input: &'r str) ->  IResult<&'r str, Response> {
261    map(
262        preceded(
263            alt((
264                tag_no_case("name:"),
265                tag_no_case("name."),
266            )),
267            not_line_ending,
268        ),
269        |s: &str| {
270            Response::FirmwareVersion(s.to_string())
271        },
272    )(input)
273}
274
275// Response to M115
276pub fn capability<'r>(input: &'r str) ->  IResult<&'r str, Response> {
277    map(
278        preceded(
279            tag_no_case("cap:"),
280            separated_pair(
281                recognize(many1(
282                    verify(
283                        anychar,
284                        |c| c.is_ascii_alphanumeric() || c == &'_',
285                    ),
286                )),
287                tag(":"),
288                one_of("01"),
289            ),
290        ),
291        |(capability, enabled): (&str, char)| {
292            Response::Capability(capability.to_string(), enabled == '1')
293        },
294    )(input)
295}
296
297pub fn unknown_resp<'r>(input: &'r str) ->  IResult<&'r str, Response> {
298    value(
299        Response::Unknown,
300        pair(
301            not(alt((
302                // Do not match partial multi-line responses as unknown
303                tag_no_case("Begin file list"),
304                tag_no_case("Deletion failed, File:"),
305                tag_no_case("echo"),
306                tag_no_case("X:"),
307                tag_no_case("T:"),
308                tag_no_case("size:"),
309            ))),
310            opt(not_line_ending),
311        ),
312    )(input)
313}
314
315pub fn f32_str<'r>() -> impl FnMut (&'r str) ->  IResult<&'r str, f32> {
316    map_res(
317        recognize(tuple((
318            opt(char('-')),
319            digit1,
320            opt(pair(
321                char('.'),
322                digit1,
323            ))
324        ))),
325        |s: &str| s.parse(),
326    )
327}
328
329pub fn u32_str<'r>() -> impl FnMut (&'r str) ->  IResult<&'r str, u32> {
330    map_res(
331        digit1,
332        |s: &str| s.parse(),
333    )
334}