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 peek(pair(
59 opt(not_line_ending),
60 line_ending,
61 )),
62 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 preceded(
117 tag("// "),
118 not_line_ending,
119 ),
120 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 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 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 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 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 tag_no_case("error:"),
204 space0,
205 ),
206 alt((
207 recognize(tuple((
210 digit1,
211 newline,
212 not_line_ending,
213 ))),
214 recognize(tuple((
217 not(digit1),
218 not_line_ending,
219 ))),
220 ))
221 ),
222 preceded(
223 pair(
224 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
259pub 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
275pub 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 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}