1use crate::{
2 lexer::{Lexer, Token, TokenType},
3 Comment, Span,
4};
5use core::fmt::{self, Display, Formatter};
6
7#[derive(Debug, Copy, Clone, PartialEq)]
10#[cfg_attr(
11 feature = "serde-1",
12 derive(serde_derive::Serialize, serde_derive::Deserialize)
13)]
14#[repr(C)]
15pub struct Word {
16 pub letter: char,
18 pub value: f32,
20 pub span: Span,
22}
23
24impl Word {
25 pub fn new(letter: char, value: f32, span: Span) -> Self {
27 Word {
28 letter,
29 value,
30 span,
31 }
32 }
33}
34
35impl Display for Word {
36 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
37 write!(f, "{}{}", self.letter, self.value)
38 }
39}
40
41#[derive(Debug, Copy, Clone, PartialEq)]
42pub(crate) enum Atom<'input> {
43 Word(Word),
44 Comment(Comment<'input>),
45 BrokenWord(Token<'input>),
47 Unknown(Token<'input>),
49}
50
51impl<'input> Atom<'input> {
52 pub(crate) fn span(&self) -> Span {
53 match self {
54 Atom::Word(word) => word.span,
55 Atom::Comment(comment) => comment.span,
56 Atom::Unknown(token) | Atom::BrokenWord(token) => token.span,
57 }
58 }
59}
60
61#[derive(Debug, Clone, PartialEq)]
62pub(crate) struct WordsOrComments<'input, I> {
63 tokens: I,
64 last_letter: Option<Token<'input>>,
67}
68
69impl<'input, I> WordsOrComments<'input, I>
70where
71 I: Iterator<Item = Token<'input>>,
72{
73 pub(crate) fn new(tokens: I) -> Self {
74 WordsOrComments {
75 tokens,
76 last_letter: None,
77 }
78 }
79}
80
81impl<'input, I> Iterator for WordsOrComments<'input, I>
82where
83 I: Iterator<Item = Token<'input>>,
84{
85 type Item = Atom<'input>;
86
87 fn next(&mut self) -> Option<Self::Item> {
88 while let Some(token) = self.tokens.next() {
89 let Token { kind, value, span } = token;
90
91 match kind {
92 TokenType::Unknown => return Some(Atom::Unknown(token)),
93 TokenType::Comment => {
94 return Some(Atom::Comment(Comment { value, span }))
95 },
96 TokenType::Letter if self.last_letter.is_none() => {
97 self.last_letter = Some(token);
98 },
99 TokenType::Number if self.last_letter.is_some() => {
100 let letter_token = self.last_letter.take().unwrap();
101 let span = letter_token.span.merge(span);
102
103 debug_assert_eq!(letter_token.value.len(), 1);
104 let letter = letter_token.value.chars().next().unwrap();
105 let value = value.parse().expect("");
106
107 return Some(Atom::Word(Word {
108 letter,
109 value,
110 span,
111 }));
112 },
113 _ => return Some(Atom::BrokenWord(token)),
114 }
115 }
116
117 self.last_letter.take().map(Atom::BrokenWord)
118 }
119}
120
121impl<'input> From<&'input str> for WordsOrComments<'input, Lexer<'input>> {
122 fn from(other: &'input str) -> WordsOrComments<'input, Lexer<'input>> {
123 WordsOrComments::new(Lexer::new(other))
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::lexer::Lexer;
131
132 #[test]
133 fn pass_comments_through() {
134 let mut words =
135 WordsOrComments::new(Lexer::new("(this is a comment) 3.14"));
136
137 let got = words.next().unwrap();
138
139 let comment = "(this is a comment)";
140 let expected = Atom::Comment(Comment {
141 value: comment,
142 span: Span {
143 start: 0,
144 end: comment.len(),
145 line: 0,
146 },
147 });
148 assert_eq!(got, expected);
149 }
150
151 #[test]
152 fn pass_garbage_through() {
153 let text = "!@#$ *";
154 let mut words = WordsOrComments::new(Lexer::new(text));
155
156 let got = words.next().unwrap();
157
158 let expected = Atom::Unknown(Token {
159 value: text,
160 kind: TokenType::Unknown,
161 span: Span {
162 start: 0,
163 end: text.len(),
164 line: 0,
165 },
166 });
167 assert_eq!(got, expected);
168 }
169
170 #[test]
171 fn numbers_are_garbage_if_they_dont_have_a_letter_in_front() {
172 let text = "3.14 ()";
173 let mut words = WordsOrComments::new(Lexer::new(text));
174
175 let got = words.next().unwrap();
176
177 let expected = Atom::BrokenWord(Token {
178 value: "3.14",
179 kind: TokenType::Number,
180 span: Span {
181 start: 0,
182 end: 4,
183 line: 0,
184 },
185 });
186 assert_eq!(got, expected);
187 }
188
189 #[test]
190 fn recognise_a_valid_word() {
191 let text = "G90";
192 let mut words = WordsOrComments::new(Lexer::new(text));
193
194 let got = words.next().unwrap();
195
196 let expected = Atom::Word(Word {
197 letter: 'G',
198 value: 90.0,
199 span: Span {
200 start: 0,
201 end: text.len(),
202 line: 0,
203 },
204 });
205 assert_eq!(got, expected);
206 }
207}