1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
use crate::{
    lexer::{Lexer, Token, TokenType},
    Comment, Span,
};
use core::fmt::{self, Display, Formatter};

/// A [`char`]-[`f32`] pair, used for things like arguments (`X3.14`), command
/// numbers (`G90`) and line numbers (`N10`).
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(
    feature = "serde-1",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[repr(C)]
pub struct Word {
    /// The letter part of this [`Word`].
    pub letter: char,
    /// The value part.
    pub value: f32,
    /// Where the [`Word`] lies in the original string.
    pub span: Span,
}

impl Word {
    /// Create a new [`Word`].
    pub fn new(letter: char, value: f32, span: Span) -> Self {
        Word {
            letter,
            value,
            span,
        }
    }
}

impl Display for Word {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}{}", self.letter, self.value)
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum Atom<'input> {
    Word(Word),
    Comment(Comment<'input>),
    /// Incomplete parts of a [`Word`].
    BrokenWord(Token<'input>),
    /// Garbage from the tokenizer (see [`TokenType::Unknown`]).
    Unknown(Token<'input>),
}

impl<'input> Atom<'input> {
    pub(crate) fn span(&self) -> Span {
        match self {
            Atom::Word(word) => word.span,
            Atom::Comment(comment) => comment.span,
            Atom::Unknown(token) | Atom::BrokenWord(token) => token.span,
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct WordsOrComments<'input, I> {
    tokens: I,
    /// keep track of the last letter so we can deal with a trailing letter
    /// that has no number
    last_letter: Option<Token<'input>>,
}

impl<'input, I> WordsOrComments<'input, I>
where
    I: Iterator<Item = Token<'input>>,
{
    pub(crate) fn new(tokens: I) -> Self {
        WordsOrComments {
            tokens,
            last_letter: None,
        }
    }
}

impl<'input, I> Iterator for WordsOrComments<'input, I>
where
    I: Iterator<Item = Token<'input>>,
{
    type Item = Atom<'input>;

    fn next(&mut self) -> Option<Self::Item> {
        while let Some(token) = self.tokens.next() {
            let Token { kind, value, span } = token;

            match kind {
                TokenType::Unknown => return Some(Atom::Unknown(token)),
                TokenType::Comment => {
                    return Some(Atom::Comment(Comment { value, span }))
                },
                TokenType::Letter if self.last_letter.is_none() => {
                    self.last_letter = Some(token);
                },
                TokenType::Number if self.last_letter.is_some() => {
                    let letter_token = self.last_letter.take().unwrap();
                    let span = letter_token.span.merge(span);

                    debug_assert_eq!(letter_token.value.len(), 1);
                    let letter = letter_token.value.chars().next().unwrap();
                    let value = value.parse().expect("");

                    return Some(Atom::Word(Word {
                        letter,
                        value,
                        span,
                    }));
                },
                _ => return Some(Atom::BrokenWord(token)),
            }
        }

        self.last_letter.take().map(Atom::BrokenWord)
    }
}

impl<'input> From<&'input str> for WordsOrComments<'input, Lexer<'input>> {
    fn from(other: &'input str) -> WordsOrComments<'input, Lexer<'input>> {
        WordsOrComments::new(Lexer::new(other))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::lexer::Lexer;

    #[test]
    fn pass_comments_through() {
        let mut words =
            WordsOrComments::new(Lexer::new("(this is a comment) 3.14"));

        let got = words.next().unwrap();

        let comment = "(this is a comment)";
        let expected = Atom::Comment(Comment {
            value: comment,
            span: Span {
                start: 0,
                end: comment.len(),
                line: 0,
            },
        });
        assert_eq!(got, expected);
    }

    #[test]
    fn pass_garbage_through() {
        let text = "!@#$ *";
        let mut words = WordsOrComments::new(Lexer::new(text));

        let got = words.next().unwrap();

        let expected = Atom::Unknown(Token {
            value: text,
            kind: TokenType::Unknown,
            span: Span {
                start: 0,
                end: text.len(),
                line: 0,
            },
        });
        assert_eq!(got, expected);
    }

    #[test]
    fn numbers_are_garbage_if_they_dont_have_a_letter_in_front() {
        let text = "3.14 ()";
        let mut words = WordsOrComments::new(Lexer::new(text));

        let got = words.next().unwrap();

        let expected = Atom::BrokenWord(Token {
            value: "3.14",
            kind: TokenType::Number,
            span: Span {
                start: 0,
                end: 4,
                line: 0,
            },
        });
        assert_eq!(got, expected);
    }

    #[test]
    fn recognise_a_valid_word() {
        let text = "G90";
        let mut words = WordsOrComments::new(Lexer::new(text));

        let got = words.next().unwrap();

        let expected = Atom::Word(Word {
            letter: 'G',
            value: 90.0,
            span: Span {
                start: 0,
                end: text.len(),
                line: 0,
            },
        });
        assert_eq!(got, expected);
    }
}