Skip to content

Commit c70543a

Browse files
committed
Add newline to grammer to explicitly address end-of-command
Fix skip_whitespace test and add respect_newlines test.
1 parent 5760850 commit c70543a

File tree

3 files changed

+59
-15
lines changed

3 files changed

+59
-15
lines changed

gcode/src/lexer.rs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub(crate) enum TokenType {
55
Letter,
66
Number,
77
Comment,
8+
Newline,
89
Unknown,
910
}
1011

@@ -16,6 +17,8 @@ impl From<char> for TokenType {
1617
TokenType::Number
1718
} else if c == '(' || c == ';' || c == ')' {
1819
TokenType::Comment
20+
} else if c == '\n' {
21+
TokenType::Newline
1922
} else {
2023
TokenType::Unknown
2124
}
@@ -53,14 +56,14 @@ impl<'input> Lexer<'input> {
5356
{
5457
let start = self.current_position;
5558
let mut end = start;
56-
let mut line_endings = 0;
5759

5860
for letter in self.rest().chars() {
5961
if !predicate(letter) {
6062
break;
6163
}
6264
if letter == '\n' {
63-
line_endings += 1;
65+
// Newline defines the command to be complete.
66+
break;
6467
}
6568
end += letter.len_utf8();
6669
}
@@ -69,7 +72,6 @@ impl<'input> Lexer<'input> {
6972
None
7073
} else {
7174
self.current_position = end;
72-
self.current_line += line_endings;
7375
Some(&self.src[start..end])
7476
}
7577
}
@@ -175,6 +177,23 @@ impl<'input> Lexer<'input> {
175177
},
176178
})
177179
}
180+
181+
fn tokenize_newline(&mut self) -> Option<Token<'input>> {
182+
let start = self.current_position;
183+
let line = self.current_line;
184+
let value = "\n";
185+
self.current_position += 1;
186+
self.current_line += 1;
187+
Some(Token {
188+
kind: TokenType::Newline,
189+
value,
190+
span: Span {
191+
start,
192+
line,
193+
end: start + 1,
194+
},
195+
})
196+
}
178197

179198
fn finished(&self) -> bool { self.current_position >= self.src.len() }
180199

@@ -219,6 +238,9 @@ impl<'input> Iterator for Lexer<'input> {
219238
TokenType::Number => {
220239
return Some(self.tokenize_number().expect(MSG))
221240
},
241+
TokenType::Newline => {
242+
return Some(self.tokenize_newline().expect(MSG))
243+
},
222244
TokenType::Unknown => self.current_position += 1,
223245
}
224246
}
@@ -253,11 +275,22 @@ mod tests {
253275

254276
#[test]
255277
fn skip_whitespace() {
256-
let mut lexer = Lexer::new(" \n\r\t ");
278+
let mut lexer = Lexer::new(" \r\t ");
257279

258280
lexer.skip_whitespace();
259281

260282
assert_eq!(lexer.current_position, lexer.src.len());
283+
assert_eq!(lexer.current_line, 0);
284+
}
285+
286+
#[test]
287+
fn respect_newlines() {
288+
let mut lexer = Lexer::new("\n\rM30garbage");
289+
290+
let token = lexer.tokenize_newline();
291+
assert_eq!(token.expect("Failed.").kind, TokenType::Newline);
292+
293+
assert_eq!(lexer.current_position, 1);
261294
assert_eq!(lexer.current_line, 1);
262295
}
263296

gcode/src/parser.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,13 @@ where
219219
// constructing
220220
let mut temp_gcode = None;
221221

222-
while let Some(next_line) = self.next_line_number() {
223-
if !line.is_empty() && next_line != line.span().line {
224-
// we've started the next line
225-
break;
226-
}
222+
if let None = self.atoms.peek() {
223+
// There is nothing left in the file. :sad-face:
224+
// This ends the parser's work.
225+
return None;
226+
}
227+
228+
while let Some(_next_line) = self.next_line_number() {
227229

228230
match self.atoms.next().expect("unreachable") {
229231
Atom::Unknown(token) => {
@@ -234,6 +236,13 @@ where
234236
self.on_comment_push_error(e.0);
235237
}
236238
},
239+
Atom::Newline(_) => {
240+
if !line.is_empty() {
241+
// Newline ends the current command
242+
// if there was something to parse.
243+
break;
244+
}
245+
},
237246
// line numbers are annoying, so handle them separately
238247
Atom::Word(word) if word.letter.to_ascii_lowercase() == 'n' => {
239248
self.handle_line_number(
@@ -255,11 +264,11 @@ where
255264
}
256265
}
257266

258-
if line.is_empty() {
259-
None
260-
} else {
261-
Some(line)
262-
}
267+
// TODO: This should exit the parser under some conditions.
268+
// IS M2 or M30: see 3.6.1.
269+
// return None;
270+
271+
return Some(line);
263272
}
264273
}
265274

@@ -422,7 +431,6 @@ mod tests {
422431
/// For some reason we were parsing the G90, then an empty G01 and the
423432
/// actual G01.
424433
#[test]
425-
#[ignore]
426434
fn funny_bug_in_crate_example() {
427435
let src = "G90 \n G01 X50.0 Y-10";
428436
let expected = vec![

gcode/src/words.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ impl Display for Word {
4242
pub(crate) enum Atom<'input> {
4343
Word(Word),
4444
Comment(Comment<'input>),
45+
Newline(Token<'input>),
4546
/// Incomplete parts of a [`Word`].
4647
BrokenWord(Token<'input>),
4748
/// Garbage from the tokenizer (see [`TokenType::Unknown`]).
@@ -53,6 +54,7 @@ impl<'input> Atom<'input> {
5354
match self {
5455
Atom::Word(word) => word.span,
5556
Atom::Comment(comment) => comment.span,
57+
Atom::Newline(newline) => newline.span,
5658
Atom::Unknown(token) | Atom::BrokenWord(token) => token.span,
5759
}
5860
}
@@ -90,6 +92,7 @@ where
9092

9193
match kind {
9294
TokenType::Unknown => return Some(Atom::Unknown(token)),
95+
TokenType::Newline => return Some(Atom::Newline(token)),
9396
TokenType::Comment => {
9497
return Some(Atom::Comment(Comment { value, span }))
9598
},

0 commit comments

Comments
 (0)