g_code/parse/
token.rs

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