gcode/
gcode.rs

1use crate::{
2    buffers::{Buffer, CapacityError, DefaultArguments},
3    Span, Word,
4};
5use core::fmt::{self, Debug, Display, Formatter};
6
7/// The general category for a [`GCode`].
8#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
9#[cfg_attr(
10    feature = "serde-1",
11    derive(serde_derive::Serialize, serde_derive::Deserialize)
12)]
13#[repr(C)]
14pub enum Mnemonic {
15    /// Preparatory commands, often telling the controller what kind of motion
16    /// or offset is desired.
17    General,
18    /// Auxilliary commands.
19    Miscellaneous,
20    /// Used to give the current program a unique "name".
21    ProgramNumber,
22    /// Tool selection.
23    ToolChange,
24}
25
26impl Mnemonic {
27    /// Try to convert a letter to its [`Mnemonic`] equivalent.
28    ///
29    /// # Examples
30    ///
31    /// ```rust
32    /// # use gcode::Mnemonic;
33    /// assert_eq!(Mnemonic::for_letter('M'), Some(Mnemonic::Miscellaneous));
34    /// assert_eq!(Mnemonic::for_letter('g'), Some(Mnemonic::General));
35    /// ```
36    pub fn for_letter(letter: char) -> Option<Mnemonic> {
37        match letter.to_ascii_lowercase() {
38            'g' => Some(Mnemonic::General),
39            'm' => Some(Mnemonic::Miscellaneous),
40            'o' => Some(Mnemonic::ProgramNumber),
41            't' => Some(Mnemonic::ToolChange),
42            _ => None,
43        }
44    }
45}
46
47impl Display for Mnemonic {
48    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
49        match self {
50            Mnemonic::General => write!(f, "G"),
51            Mnemonic::Miscellaneous => write!(f, "M"),
52            Mnemonic::ProgramNumber => write!(f, "O"),
53            Mnemonic::ToolChange => write!(f, "T"),
54        }
55    }
56}
57
58/// The in-memory representation of a single command in the G-code language
59/// (e.g. `"G01 X50.0 Y-20.0"`).
60#[derive(Clone)]
61#[cfg_attr(
62    feature = "serde-1",
63    derive(serde_derive::Serialize, serde_derive::Deserialize)
64)]
65pub struct GCode<A = DefaultArguments> {
66    mnemonic: Mnemonic,
67    number: f32,
68    arguments: A,
69    span: Span,
70}
71
72impl GCode {
73    /// Create a new [`GCode`] which uses the [`DefaultArguments`] buffer.
74    pub fn new(mnemonic: Mnemonic, number: f32, span: Span) -> Self {
75        GCode {
76            mnemonic,
77            number,
78            span,
79            arguments: DefaultArguments::default(),
80        }
81    }
82}
83
84impl<A: Buffer<Word>> GCode<A> {
85    /// Create a new [`GCode`] which uses a custom [`Buffer`].
86    pub fn new_with_argument_buffer(
87        mnemonic: Mnemonic,
88        number: f32,
89        span: Span,
90        arguments: A,
91    ) -> Self {
92        GCode {
93            mnemonic,
94            number,
95            span,
96            arguments,
97        }
98    }
99
100    /// The overall category this [`GCode`] belongs to.
101    pub fn mnemonic(&self) -> Mnemonic { self.mnemonic }
102
103    /// The integral part of a command number (i.e. the `12` in `G12.3`).
104    pub fn major_number(&self) -> u32 {
105        debug_assert!(self.number >= 0.0);
106
107        libm::floorf(self.number) as u32
108    }
109
110    /// The fractional part of a command number (i.e. the `3` in `G12.3`).
111    pub fn minor_number(&self) -> u32 {
112        let fract = self.number - libm::floorf(self.number);
113        let digit = libm::roundf(fract * 10.0);
114        digit as u32
115    }
116
117    /// The arguments attached to this [`GCode`].
118    pub fn arguments(&self) -> &[Word] { self.arguments.as_slice() }
119
120    /// Where the [`GCode`] was found in its source text.
121    pub fn span(&self) -> Span { self.span }
122
123    /// Add an argument to the list of arguments attached to this [`GCode`].
124    pub fn push_argument(
125        &mut self,
126        arg: Word,
127    ) -> Result<(), CapacityError<Word>> {
128        self.span = self.span.merge(arg.span);
129        self.arguments.try_push(arg)
130    }
131
132    /// The builder equivalent of [`GCode::push_argument()`].
133    ///
134    /// # Panics
135    ///
136    /// This will panic if the underlying [`Buffer`] returns a
137    /// [`CapacityError`].
138    pub fn with_argument(mut self, arg: Word) -> Self {
139        if let Err(e) = self.push_argument(arg) {
140            panic!("Unable to add the argument {:?}: {}", arg, e);
141        }
142        self
143    }
144
145    /// Get the value for a particular argument.
146    ///
147    /// # Examples
148    ///
149    /// ```rust
150    /// # use gcode::{GCode, Mnemonic, Span, Word};
151    /// let gcode = GCode::new(Mnemonic::General, 1.0, Span::PLACEHOLDER)
152    ///     .with_argument(Word::new('X', 30.0, Span::PLACEHOLDER))
153    ///     .with_argument(Word::new('Y', -3.14, Span::PLACEHOLDER));
154    ///
155    /// assert_eq!(gcode.value_for('Y'), Some(-3.14));
156    /// ```
157    pub fn value_for(&self, letter: char) -> Option<f32> {
158        let letter = letter.to_ascii_lowercase();
159
160        for arg in self.arguments() {
161            if arg.letter.to_ascii_lowercase() == letter {
162                return Some(arg.value);
163            }
164        }
165
166        None
167    }
168}
169
170impl<A: Buffer<Word>> Extend<Word> for GCode<A> {
171    fn extend<I: IntoIterator<Item = Word>>(&mut self, words: I) {
172        for word in words {
173            if self.push_argument(word).is_err() {
174                // we can't add any more arguments
175                return;
176            }
177        }
178    }
179}
180
181impl<A: Buffer<Word>> Debug for GCode<A> {
182    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
183        // we manually implement Debug because the the derive will constrain
184        // the buffer type to be Debug, which isn't necessary and actually makes
185        // it impossible to print something like ArrayVec<[T; 128]>
186        let GCode {
187            mnemonic,
188            number,
189            arguments,
190            span,
191        } = self;
192
193        f.debug_struct("GCode")
194            .field("mnemonic", mnemonic)
195            .field("number", number)
196            .field("arguments", &crate::buffers::debug(arguments))
197            .field("span", span)
198            .finish()
199    }
200}
201
202impl<A: Buffer<Word>> Display for GCode<A> {
203    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
204        write!(f, "{}{}", self.mnemonic(), self.major_number())?;
205
206        if self.minor_number() != 0 {
207            write!(f, ".{}", self.minor_number())?;
208        }
209
210        for arg in self.arguments() {
211            write!(f, " {}", arg)?;
212        }
213
214        Ok(())
215    }
216}
217
218impl<A, B> PartialEq<GCode<B>> for GCode<A>
219where
220    A: Buffer<Word>,
221    B: Buffer<Word>,
222{
223    fn eq(&self, other: &GCode<B>) -> bool {
224        let GCode {
225            mnemonic,
226            number,
227            arguments,
228            span,
229        } = self;
230
231        *span == other.span()
232            && *mnemonic == other.mnemonic
233            && *number == other.number
234            && arguments.as_slice() == other.arguments.as_slice()
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use arrayvec::ArrayVec;
242    use std::prelude::v1::*;
243
244    type BigBuffer = ArrayVec<[Word; 32]>;
245
246    #[test]
247    fn correct_major_number() {
248        let code = GCode {
249            mnemonic: Mnemonic::General,
250            number: 90.5,
251            arguments: BigBuffer::default(),
252            span: Span::default(),
253        };
254
255        assert_eq!(code.major_number(), 90);
256    }
257
258    #[test]
259    fn correct_minor_number() {
260        for i in 0..=9 {
261            let code = GCode {
262                mnemonic: Mnemonic::General,
263                number: 10.0 + (i as f32) / 10.0,
264                arguments: BigBuffer::default(),
265                span: Span::default(),
266            };
267
268            assert_eq!(code.minor_number(), i);
269        }
270    }
271
272    #[test]
273    fn get_argument_values() {
274        let mut code = GCode::new_with_argument_buffer(
275            Mnemonic::General,
276            90.0,
277            Span::default(),
278            BigBuffer::default(),
279        );
280        code.push_argument(Word {
281            letter: 'X',
282            value: 10.0,
283            span: Span::default(),
284        })
285        .unwrap();
286        code.push_argument(Word {
287            letter: 'y',
288            value: -3.5,
289            span: Span::default(),
290        })
291        .unwrap();
292
293        assert_eq!(code.value_for('X'), Some(10.0));
294        assert_eq!(code.value_for('x'), Some(10.0));
295        assert_eq!(code.value_for('Y'), Some(-3.5));
296        assert_eq!(code.value_for('Z'), None);
297    }
298}