g_code/parse/
ast.rs

1use crate::emit::Token;
2
3use super::token::*;
4use std::{fmt::Debug, iter::Peekable};
5
6#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
7/// A range of [u8] in the raw text of the program.
8/// Useful for providing diagnostic information in
9/// higher-level tooling.
10///
11/// The end of the span is exclusive.
12pub struct Span(pub usize, pub usize);
13
14impl Span {
15    pub fn len(&self) -> usize {
16        self.1.saturating_sub(self.0)
17    }
18
19    pub fn is_empty(&self) -> bool {
20        self.1 <= self.0
21    }
22}
23
24pub trait Spanned {
25    fn span(&self) -> Span;
26}
27
28impl std::ops::Add for Span {
29    type Output = Self;
30    fn add(self, rhs: Self) -> Self {
31        Self(self.0.min(rhs.0), self.1.max(rhs.1))
32    }
33}
34impl std::ops::AddAssign for Span {
35    fn add_assign(&mut self, rhs: Self) {
36        *self = Self(self.0.min(rhs.0), self.1.max(rhs.1))
37    }
38}
39
40impl From<Span> for std::ops::Range<usize> {
41    fn from(span: Span) -> Self {
42        span.0..span.1
43    }
44}
45
46impl<T: Spanned> Spanned for &T {
47    fn span(&self) -> Span {
48        <T as Spanned>::span(*self)
49    }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53/// Representation of a sequence of g-code logically organized as a file.
54/// This may also be referred to as a program.
55pub struct File<'input> {
56    pub(crate) percents: Vec<Percent>,
57    pub(crate) lines: Vec<(Line<'input>, Newline)>,
58    pub(crate) last_line: Option<Line<'input>>,
59    pub(crate) span: Span,
60}
61
62impl<'input> File<'input> {
63    /// Iterate by [Line].
64    /// The last [Line] may or may not be followed by a [Newline].
65    pub fn iter(&self) -> impl Iterator<Item = &Line<'input>> {
66        self.lines
67            .iter()
68            .map(|(line, _)| line)
69            .chain(self.last_line.iter())
70    }
71
72    /// Iterating by [Field] in a file.
73    pub fn iter_fields(&self) -> impl Iterator<Item = &Field<'input>> {
74        self.iter().flat_map(Line::iter_fields)
75    }
76
77    /// Iterating by [Flag] in a file.
78    pub fn iter_flags(&self) -> impl Iterator<Item = &Flag<'input>> {
79        self.iter().flat_map(Line::iter_flags)
80    }
81
82    /// Iterate by [InlineComment] in a file.
83    pub fn iter_inline_comments(&self) -> impl Iterator<Item = &InlineComment<'input>> {
84        self.iter().flat_map(Line::iter_inline_comments)
85    }
86
87    /// Iterate by [Whitespace] in a file.
88    pub fn iter_whitespace(&self) -> impl Iterator<Item = &Whitespace<'input>> {
89        self.iter().flat_map(Line::iter_whitespace)
90    }
91
92    /// Iterate by [Comment] in a file.
93    pub fn iter_comments(&self) -> impl Iterator<Item = &Comment<'input>> {
94        self.iter().filter_map(|l| l.comment.as_ref())
95    }
96
97    /// Iterate by [Checksum] in a file.
98    pub fn iter_checksums(&self) -> impl Iterator<Item = &Checksum> {
99        self.iter().filter_map(|l| l.checksum.as_ref())
100    }
101
102    /// Iterate by [u8] in the file.
103    pub fn iter_bytes(&self) -> impl Iterator<Item = &u8> {
104        self.iter().flat_map(|line| line.iter_bytes())
105    }
106
107    /// Iterate by emission [Token].
108    pub fn iter_emit_tokens<'a>(&'a self) -> impl Iterator<Item = Token<'input>> + 'a {
109        TokenizingIterator {
110            field_iterator: self.iter_fields().peekable(),
111            flag_iterator: self.iter_flags().peekable(),
112            inline_comment_iterator: self.iter_inline_comments().peekable(),
113            comment_iterator: self.iter_comments().peekable(),
114        }
115    }
116}
117
118impl<'input> Spanned for File<'input> {
119    fn span(&self) -> Span {
120        self.span
121    }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125/// A sequence of g-code that may be inserted into a file.
126///
127/// This might be used when verifying user-supplied tool
128/// start/stop sequences.
129pub struct Snippet<'input> {
130    pub(crate) lines: Vec<(Line<'input>, Newline)>,
131    pub(crate) last_line: Option<Line<'input>>,
132    pub(crate) span: Span,
133}
134
135impl<'input> Snippet<'input> {
136    /// Iterate by [Line].
137    /// The last [Line] may or may not be followed by a [Newline].
138    pub fn iter(&self) -> impl Iterator<Item = &Line<'input>> {
139        self.lines
140            .iter()
141            .map(|(line, _)| line)
142            .chain(self.last_line.iter())
143    }
144
145    /// Iterating by [Field] in a snippet.
146    pub fn iter_fields(&self) -> impl Iterator<Item = &Field<'input>> {
147        self.iter().flat_map(Line::iter_fields)
148    }
149
150    /// Iterating by [Flag] in a snippet.
151    pub fn iter_flags(&self) -> impl Iterator<Item = &Flag<'input>> {
152        self.iter().flat_map(Line::iter_flags)
153    }
154
155    /// Iterate by [InlineComment] in a snippet.
156    pub fn iter_inline_comments(&self) -> impl Iterator<Item = &InlineComment<'input>> {
157        self.iter().flat_map(Line::iter_inline_comments)
158    }
159
160    /// Iterate by [Whitespace] in a snippet.
161    pub fn iter_whitespace(&self) -> impl Iterator<Item = &Whitespace<'input>> {
162        self.iter().flat_map(Line::iter_whitespace)
163    }
164
165    /// Iterate by [u8] in the snippet.
166    pub fn iter_bytes(&self) -> impl Iterator<Item = &u8> {
167        self.iter().flat_map(|line| line.iter_bytes())
168    }
169
170    /// Iterate by [Comment] in the snippet.
171    pub fn iter_comments(&self) -> impl Iterator<Item = &Comment<'input>> {
172        self.iter().filter_map(|l| l.comment.as_ref())
173    }
174
175    /// Iterate by [Checksum] in the snippet.
176    pub fn iter_checksums(&self) -> impl Iterator<Item = &Checksum> {
177        self.iter().filter_map(|l| l.checksum.as_ref())
178    }
179
180    /// Iterate by emission [Token].
181    pub fn iter_emit_tokens<'a>(&'a self) -> impl Iterator<Item = Token<'input>> + 'a {
182        TokenizingIterator {
183            field_iterator: self.iter_fields().peekable(),
184            flag_iterator: self.iter_flags().peekable(),
185            inline_comment_iterator: self.iter_inline_comments().peekable(),
186            comment_iterator: self.iter_comments().peekable(),
187        }
188    }
189}
190
191impl<'input> Spanned for Snippet<'input> {
192    fn span(&self) -> Span {
193        self.span
194    }
195}
196#[derive(Debug, Clone, PartialEq, Eq)]
197/// A sequence of g-code that is either followed by a [Newline] or at the end of a file.
198pub struct Line<'input> {
199    pub(crate) line_components: Vec<LineComponent<'input>>,
200    pub(crate) checksum: Option<Checksum>,
201    pub(crate) comment: Option<Comment<'input>>,
202    pub(crate) span: Span,
203}
204
205impl<'input> Spanned for Line<'input> {
206    fn span(&self) -> Span {
207        self.span
208    }
209}
210
211impl<'input> Line<'input> {
212    /// Iterate by [Field] in a line of g-code.
213    pub fn iter_fields(&self) -> impl Iterator<Item = &Field<'input>> {
214        self.line_components.iter().filter_map(|c| c.field.as_ref())
215    }
216
217    /// Iterate by [Flag] in a line of g-code.
218    pub fn iter_flags(&self) -> impl Iterator<Item = &Flag<'input>> {
219        self.line_components.iter().filter_map(|c| c.flag.as_ref())
220    }
221
222    /// Iterate by [InlineComment] in a line of g-code.
223    pub fn iter_inline_comments(&self) -> impl Iterator<Item = &InlineComment<'input>> {
224        self.line_components
225            .iter()
226            .filter_map(|c| c.inline_comment.as_ref())
227    }
228
229    /// Iterate by [Whitespace] in a line of g-code.
230    pub fn iter_whitespace(&self) -> impl Iterator<Item = &Whitespace<'input>> {
231        self.line_components
232            .iter()
233            .filter_map(|c| c.whitespace.as_ref())
234    }
235
236    /// Iterate by emission [Token] in a line of g-code.
237    pub fn iter_emit_tokens<'a>(&'a self) -> impl Iterator<Item = Token<'input>> + 'a {
238        TokenizingIterator {
239            field_iterator: self.iter_fields().peekable(),
240            flag_iterator: self.iter_flags().peekable(),
241            inline_comment_iterator: self.iter_inline_comments().peekable(),
242            comment_iterator: self.comment.iter().peekable(),
243        }
244    }
245
246    /// Validate the line's checksum, if any, against its fields.
247    ///
248    /// Returns [None] if there is no checksum.
249    ///
250    /// If the line does have a checksum, this will return an empty [Ok]
251    /// or an [Err] containing the computed checksum that differs from the actual.
252    pub fn validate_checksum(&self) -> Option<Result<(), u8>> {
253        if let Some(Checksum {
254            inner: checksum, ..
255        }) = self.checksum.as_ref()
256        {
257            let computed_checksum = self.compute_checksum();
258            if computed_checksum != *checksum {
259                return Some(Err(computed_checksum));
260            } else {
261                return Some(Ok(()));
262            }
263        }
264        None
265    }
266
267    /// Iterate over [u8] in a [Line].
268    pub fn iter_bytes(&self) -> impl Iterator<Item = &u8> {
269        self.line_components.iter().flat_map(|c| c.iter_bytes())
270    }
271
272    /// XORs bytes in a [Line] leading up to the asterisk of a [`Checksum`].
273    pub fn compute_checksum(&self) -> u8 {
274        let take = if let Some(checksum) = &self.checksum {
275            checksum.span.0
276        } else if let Some(comment) = &self.comment {
277            comment.pos
278        } else {
279            self.span.1
280        } - self.span.0;
281        self.iter_bytes().take(take).fold(0u8, |acc, b| acc ^ b)
282    }
283}
284
285struct TokenizingIterator<'a, 'input: 'a, F, FL, IC, C>
286where
287    F: Iterator<Item = &'a Field<'input>>,
288    FL: Iterator<Item = &'a Flag<'input>>,
289    IC: Iterator<Item = &'a InlineComment<'input>>,
290    C: Iterator<Item = &'a Comment<'input>>,
291{
292    field_iterator: Peekable<F>,
293    flag_iterator: Peekable<FL>,
294    inline_comment_iterator: Peekable<IC>,
295    comment_iterator: Peekable<C>,
296}
297
298impl<'a, 'input: 'a, F, FL, IC, C> Iterator for TokenizingIterator<'a, 'input, F, FL, IC, C>
299where
300    F: Iterator<Item = &'a Field<'input>>,
301    FL: Iterator<Item = &'a Flag<'input>>,
302    IC: Iterator<Item = &'a InlineComment<'input>>,
303    C: Iterator<Item = &'a Comment<'input>>,
304{
305    type Item = Token<'input>;
306
307    fn next(&mut self) -> Option<Self::Item> {
308        let spans = [
309            self.field_iterator.peek().map(Spanned::span),
310            self.flag_iterator.peek().map(Spanned::span),
311            self.inline_comment_iterator.peek().map(Spanned::span),
312            self.comment_iterator.peek().map(Spanned::span),
313        ];
314        if let Some((i, _)) = spans
315            .iter()
316            .enumerate()
317            .filter(|(_, span)| span.is_some())
318            .min_by_key(|(_, span)| span.unwrap().0)
319        {
320            match i {
321                0 => Some(Token::from(self.field_iterator.next().unwrap())),
322                1 => Some(Token::from(self.flag_iterator.next().unwrap())),
323                2 => Some(Token::from(self.inline_comment_iterator.next().unwrap())),
324                3 => Some(Token::from(self.comment_iterator.next().unwrap())),
325                _ => unreachable!(),
326            }
327        } else {
328            None
329        }
330    }
331}