g_code/emit/
token.rs

1use rust_decimal::prelude::ToPrimitive;
2use rust_decimal::Decimal;
3
4use std::borrow::Cow;
5
6use crate::parse::token::{
7    Comment as ParsedComment, Field as ParsedField, Flag as ParsedFlag,
8    InlineComment as ParsedInlineComment, Value as ParsedValue,
9};
10
11#[derive(Clone, PartialEq, Debug)]
12/// The output struct for g-code emission implementing [std::fmt::Display]
13///
14/// Any strings here are expected to have escaped characters, see <https://www.reprap.org/wiki/G-code#Quoted_strings>
15pub enum Token<'a> {
16    Field(Field<'a>),
17    Flag(Flag<'a>),
18    Comment {
19        is_inline: bool,
20        inner: Cow<'a, str>,
21    },
22}
23
24impl<'input> From<&ParsedField<'input>> for Token<'input> {
25    fn from(field: &ParsedField<'input>) -> Self {
26        Self::Field(field.into())
27    }
28}
29
30impl<'input> From<&ParsedFlag<'input>> for Token<'input> {
31    fn from(flag: &ParsedFlag<'input>) -> Self {
32        Self::Flag(flag.into())
33    }
34}
35
36impl<'a, 'input: 'a> From<&'a ParsedInlineComment<'input>> for Token<'input> {
37    fn from(comment: &'a ParsedInlineComment<'input>) -> Self {
38        Self::Comment {
39            is_inline: true,
40            inner: Cow::Borrowed(
41                comment
42                    .inner
43                    .strip_prefix('(')
44                    .unwrap()
45                    .strip_suffix(')')
46                    .unwrap(),
47            ),
48        }
49    }
50}
51
52impl<'input> From<&ParsedComment<'input>> for Token<'input> {
53    fn from(comment: &ParsedComment<'input>) -> Self {
54        Self::Comment {
55            is_inline: false,
56            inner: Cow::Borrowed(comment.inner.strip_prefix(';').unwrap()),
57        }
58    }
59}
60
61/// Fundamental unit of g-code: a descriptive letter followed by a value.
62///
63/// Field type supports owned and partially-borrowed representations using [Cow].
64#[derive(Clone, PartialEq, Debug)]
65pub struct Field<'a> {
66    pub letters: Cow<'a, str>,
67    pub value: Value<'a>,
68}
69
70impl<'input> From<&ParsedField<'input>> for Field<'input> {
71    fn from(field: &ParsedField<'input>) -> Self {
72        Self {
73            letters: field.letters.into(),
74            value: Value::from(&field.value),
75        }
76    }
77}
78
79impl<'a> From<Field<'a>> for Token<'a> {
80    fn from(field: Field<'a>) -> Token<'a> {
81        Self::Field(field)
82    }
83}
84
85impl<'a> Field<'a> {
86    /// Returns an owned representation of the Field valid for the `'static` lifetime.
87    ///
88    /// This will allocate any string types.
89    pub fn into_owned(self) -> Field<'static> {
90        Field {
91            letters: self.letters.into_owned().into(),
92            value: self.value.into_owned(),
93        }
94    }
95}
96
97#[derive(Clone, PartialEq, Debug)]
98pub struct Flag<'a> {
99    pub letter: Cow<'a, str>,
100}
101
102impl<'input> From<&ParsedFlag<'input>> for Flag<'input> {
103    fn from(flag: &ParsedFlag<'input>) -> Self {
104        Self {
105            letter: flag.letter.into(),
106        }
107    }
108}
109
110/// All the possible variations of a field's value.
111/// Some flavors of g-code also allow for strings.
112///
113/// Any strings here are expected to have escaped characters, see <https://www.reprap.org/wiki/G-code#Quoted_strings>
114#[derive(Clone, PartialEq, Debug)]
115pub enum Value<'a> {
116    Rational(Decimal),
117    Float(f64),
118    Integer(usize),
119    String(Cow<'a, str>),
120}
121
122impl Value<'_> {
123    /// Interpret the value as an [f64]
124    ///
125    /// Returns [Option::None] for [Value::String] or a [Value::Rational] that can't be converted.
126    pub fn as_f64(&self) -> Option<f64> {
127        match self {
128            Self::Rational(r) => r.to_f64(),
129            Self::Integer(i) => Some(*i as f64),
130            Self::Float(f) => Some(*f),
131            Self::String(_) => None,
132        }
133    }
134
135    /// Returns an owned representation of the Value valid for the `'static` lifetime.
136    ///
137    /// This will allocate a string for a [Value::String].
138    pub fn into_owned(self) -> Value<'static> {
139        match self {
140            Self::String(s) => Value::String(s.into_owned().into()),
141            Self::Rational(r) => Value::Rational(r),
142            Self::Integer(i) => Value::Integer(i),
143            Self::Float(f) => Value::Float(f),
144        }
145    }
146}
147
148impl<'input> From<&ParsedValue<'input>> for Value<'input> {
149    fn from(val: &ParsedValue<'input>) -> Self {
150        use ParsedValue::*;
151        match val {
152            Rational(r) => Self::Rational(*r),
153            Integer(i) => Self::Integer(*i),
154            String(s) => {
155                // Remove enclosing quotes
156                Self::String(Cow::Borrowed(
157                    s.strip_prefix('"').unwrap().strip_suffix('"').unwrap(),
158                ))
159            }
160        }
161    }
162}