Skip to main content

g_code/parse/
token.rs

1use std::cmp::PartialEq;
2
3use rust_decimal::Decimal;
4
5use super::ast::{Span, Spanned};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8/// ASCII letter(s) or `$` followed by a [Value]
9pub struct Field<'input> {
10    pub(crate) letters: &'input str,
11    pub(crate) value: Value<'input>,
12    pub(crate) raw_value: Vec<&'input str>,
13    pub(crate) span: Span,
14}
15
16impl<'input> Field<'input> {
17    /// Iterate over [u8] in a [Field].
18    pub fn iter_bytes(&'input self) -> impl Iterator<Item = &'input u8> {
19        self.letters
20            .as_bytes()
21            .iter()
22            .chain(self.raw_value.iter().flat_map(|s| s.as_bytes().iter()))
23    }
24}
25
26impl Spanned for Field<'_> {
27    fn span(&self) -> Span {
28        self.span
29    }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct Flag<'input> {
34    pub(crate) letter: &'input str,
35    pub(crate) span: Span,
36}
37
38impl<'input> Flag<'input> {
39    /// Iterate over [u8] in a [Field].
40    pub fn iter_bytes(&'input self) -> impl Iterator<Item = &'input u8> {
41        self.letter.as_bytes().iter()
42    }
43}
44
45impl Spanned for Flag<'_> {
46    fn span(&self) -> Span {
47        self.span
48    }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum Value<'input> {
53    /// A real number g-code value.
54    ///
55    /// While this is often an [f64] or [f32],
56    /// that was converted to a string,
57    /// it is parsed as a [Decimal] to
58    /// ensure numerical stability.
59    Rational(Decimal),
60    /// An unsigned integer g-code value fitting in a [usize].
61    /// For instance, this would be the 0 in G0.
62    Integer(usize),
63    /// A [string](str) g-code value.
64    ///
65    /// Delimiting quotes are included in the value
66    /// and escaped quotes are NOT unescaped.
67    String(&'input str),
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Checksum {
72    // Note for readers:
73    // this is not stored as a str because any
74    // leading zeros do not affect the checksum
75    pub(crate) inner: u8,
76    pub(crate) span: Span,
77}
78
79impl Spanned for Checksum {
80    fn span(&self) -> Span {
81        self.span
82    }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86/// A `\n` or `\r\n` token that delimits instances of [super::ast::Line] in a [super::ast::File].
87pub struct Newline {
88    pub(crate) pos: usize,
89}
90
91impl Spanned for Newline {
92    fn span(&self) -> Span {
93        Span(self.pos, self.pos + 1)
94    }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98/// A `%` token that wraps a g-code [super::ast::File].
99pub struct Percent {
100    pub(crate) pos: usize,
101}
102
103impl Spanned for Percent {
104    fn span(&self) -> Span {
105        Span(self.pos, self.pos + 1)
106    }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
110/// Any sequence of ASCII whitespace except for [Newline].
111pub struct Whitespace<'input> {
112    pub(crate) inner: &'input str,
113    pub(crate) pos: usize,
114}
115
116impl<'input> Whitespace<'input> {
117    pub fn iter_bytes(&'input self) -> impl Iterator<Item = &'input u8> {
118        self.inner.as_bytes().iter()
119    }
120}
121
122impl Spanned for Whitespace<'_> {
123    fn span(&self) -> Span {
124        Span(self.pos, self.pos + self.inner.len())
125    }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
129/// A semicolon `;` followed by ASCII characters and terminated by a [Newline]
130/// or the end of the file.
131///
132/// The semicolon is part of the inner representation.
133///
134/// Some machines/programs will display these comments
135/// as the g-code is executed.
136pub struct Comment<'input> {
137    pub(crate) inner: &'input str,
138    pub(crate) pos: usize,
139}
140
141impl<'input> Comment<'input> {
142    pub fn iter_bytes(&'input self) -> impl Iterator<Item = &'input u8> {
143        self.inner.as_bytes().iter()
144    }
145}
146
147impl Spanned for Comment<'_> {
148    fn span(&self) -> Span {
149        Span(self.pos, self.pos + self.inner.len())
150    }
151}
152
153/// An opening parenthesis `(` followed by ASCII characters and terminated
154/// by a closing parenthesis `)`.
155/// A [Newline] is not allowed in an inline comment.
156///
157/// The parentheses are part of the inner representation.
158#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct InlineComment<'input> {
160    pub(crate) inner: &'input str,
161    pub(crate) pos: usize,
162}
163
164impl<'input> InlineComment<'input> {
165    pub fn iter_bytes(&'input self) -> impl Iterator<Item = &'input u8> {
166        self.inner.as_bytes().iter()
167    }
168}
169
170impl Spanned for InlineComment<'_> {
171    fn span(&self) -> Span {
172        Span(self.pos, self.pos + self.inner.len())
173    }
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Default)]
177/// An internal structure used to make writing the [peg] parser easier.
178pub struct LineComponent<'input> {
179    pub(crate) field: Option<Field<'input>>,
180    pub(crate) flag: Option<Flag<'input>>,
181    pub(crate) whitespace: Option<Whitespace<'input>>,
182    pub(crate) inline_comment: Option<InlineComment<'input>>,
183}
184
185impl<'input> LineComponent<'input> {
186    pub fn iter_bytes(&'input self) -> impl Iterator<Item = &'input u8> + 'input {
187        self.field
188            .iter()
189            .flat_map(|f| f.iter_bytes())
190            .chain(self.flag.iter().flat_map(|f| f.iter_bytes()))
191            .chain(self.whitespace.iter().flat_map(|w| w.iter_bytes()))
192            .chain(self.inline_comment.iter().flat_map(|i| i.iter_bytes()))
193    }
194}