nom_gcode/
lib.rs

1extern crate nom;
2
3use std::fmt;
4use thiserror::Error;
5
6mod mnemonic;
7pub use mnemonic::*;
8
9mod parse_command;
10pub use parse_command::parse_command;
11
12mod parse_args;
13pub use parse_args::parse_args;
14pub use parse_args::parse_kv_arg;
15
16mod parse_comments;
17pub use parse_comments::*;
18
19mod parse_gcode;
20pub use parse_gcode::parse_gcode;
21
22#[derive(Error, Debug)]
23pub enum GCodeParseError {
24    #[error("Invalid GCode. GCodes must start with a letter, a number and a space. Got: {0}")]
25    InvalidGCode(String),
26    #[error("Badly formatted GCode arguments. Got: {0}")]
27    InvalidArguments(String),
28    #[error("Badly formatted GCode comment. Got: {0}")]
29    InvalidComment(String),
30}
31
32#[derive(Debug, PartialEq, Clone)]
33pub struct Comment<'r>(
34    pub &'r str
35);
36
37#[derive(Debug, PartialEq, Clone)]
38pub enum DocComment<'r> {
39    GCodeFlavor(&'r str),
40    PrintTime(std::time::Duration),
41    FilamentUsed { meters: f64 },
42    LayerHeight { millis: f64 },
43}
44
45#[derive(Debug, PartialEq, Clone)]
46pub enum GCodeLine<'r> {
47    /// The first non-blank line of a file may contain nothing but a percent sign, %, possibly
48    /// surrounded by white space, and later in the file (normally at the end of the file) there
49    /// may be a similar line.
50    ///
51    /// http://linuxcnc.org/docs/html/gcode/overview.html
52    FileDemarcator,
53    GCode(GCode<'r>),
54    Comment(Comment<'r>),
55    DocComment(DocComment<'r>),
56}
57
58impl<'r> From<Comment<'r>> for GCodeLine<'r> {
59    fn from(comment: Comment<'r>) -> Self {
60        GCodeLine::Comment(comment)
61    }
62}
63
64#[derive(Debug, PartialEq, Clone)]
65pub struct GCode<'r> {
66    pub line_number: Option<u32>,
67    pub mnemonic: Mnemonic,
68    pub major: u32,
69    pub minor: u32,
70    args_or_comments: Option<Vec<ArgOrComment<'r>>>,
71}
72
73#[derive(Debug, PartialEq, Clone)]
74pub enum ArgOrComment<'r> {
75    KeyValue(KeyValue),
76    TextArg(&'r str),
77    Comment(Comment<'r>),
78}
79
80pub type KeyValue = (char, Option<f32>);
81
82impl<'r> fmt::Display for GCode<'r> {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        let gcode = format!("{}{}.{}", self.mnemonic, self.major, self.minor);
85
86        let mut words = vec![gcode];
87
88        let arg_words = self.arguments()
89            .map(|(k, v)| {
90                format!("{}{}", k, v.map(|f| f.to_string()).unwrap_or("".to_string()))
91            });
92
93        words.extend(arg_words);
94
95        if let Some(text) = self.text() {
96            words.push(text.to_string());
97        };
98
99        write!(f, "{}", words.join(" "))
100    }
101}
102
103impl<'r> GCode<'r> {
104    // #[inline(always)]
105    fn args_or_comments_iter(&self) -> impl Iterator<Item = &ArgOrComment<'r>> {
106        use std::convert::identity;
107
108        self.args_or_comments
109            .iter()
110            .flat_map(identity)
111    }
112
113    // #[inline(always)]
114    pub fn text(&self) -> Option<&'r str> {
115        self.args_or_comments_iter()
116            .find_map(|ac| {
117                if let ArgOrComment::TextArg(text) = ac {
118                    Some(*text)
119                } else {
120                    None
121                }
122            })
123    }
124
125    // #[inline(always)]
126    pub fn arguments(&self) -> impl Iterator<Item = &KeyValue> {
127        self.args_or_comments_iter()
128            .filter_map(|ac| {
129                if let ArgOrComment::KeyValue(arg)  = ac {
130                    Some(arg)
131                } else {
132                    None
133                }
134            })
135    }
136}