Skip to main content

g_code/emit/
token.rs

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