1use crate::{Span, Spanned, StepError};
7use std::borrow::Cow;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum Token<'a> {
12 Id(Cow<'a, [u8]>),
14 Text(Cow<'a, [u8]>),
16 Binary(Cow<'a, [u8]>),
18 Keyword(Cow<'a, [u8]>),
20 Name(Cow<'a, [u8]>),
22 Integer(Cow<'a, [u8]>),
24 Real(Cow<'a, [u8]>),
26 Dollar,
28 Star,
30 OpenParen,
32 CloseParen,
34 Comma,
36 Equals,
38 Semicolon,
40}
41
42#[derive(Debug, Clone)]
44pub struct Lexer<'a> {
45 input: &'a [u8],
46 position: usize,
47 finished: bool,
48}
49
50fn is_valid_binary(body: &[u8]) -> bool {
51 matches!(body.first(), Some(b'0'..=b'3')) && body[1..].iter().all(u8::is_ascii_hexdigit)
52}
53
54const fn is_ignored_control(byte: u8) -> bool {
55 matches!(byte, b'\t' | b'\n' | b'\r' | 0x0c)
56}
57
58fn strip_print_directives(bytes: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
59 if !bytes
60 .windows(3)
61 .any(|window| matches!(window, b"\\N\\" | b"\\F\\"))
62 {
63 return bytes;
64 }
65 let mut stripped = Vec::with_capacity(bytes.len());
66 let mut position = 0;
67 while position < bytes.len() {
68 if matches!(bytes.get(position..position + 3), Some(b"\\N\\" | b"\\F\\")) {
69 position += 3;
70 } else {
71 stripped.push(bytes[position]);
72 position += 1;
73 }
74 }
75 Cow::Owned(stripped)
76}
77
78fn strip_text_print_directives(bytes: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
79 let mut stripped: Option<Vec<u8>> = None;
80 let mut position = 0;
81 while position < bytes.len() {
82 if bytes.get(position..position + 2) == Some(b"\\\\") {
83 if let Some(output) = &mut stripped {
84 output.extend_from_slice(b"\\\\");
85 }
86 position += 2;
87 } else if matches!(bytes.get(position..position + 3), Some(b"\\N\\" | b"\\F\\")) {
88 stripped.get_or_insert_with(|| {
89 let mut output = Vec::with_capacity(bytes.len());
90 output.extend_from_slice(&bytes[..position]);
91 output
92 });
93 position += 3;
94 } else {
95 if let Some(output) = &mut stripped {
96 output.push(bytes[position]);
97 }
98 position += 1;
99 }
100 }
101 stripped.map_or(bytes, Cow::Owned)
102}
103
104impl<'a> Lexer<'a> {
105 #[must_use]
107 pub const fn new(input: &'a [u8]) -> Self {
108 Self {
109 input,
110 position: 0,
111 finished: false,
112 }
113 }
114
115 #[must_use]
117 pub const fn offset(&self) -> usize {
118 self.position
119 }
120
121 pub(crate) fn resume_at(&mut self, position: usize) {
127 self.position = position.min(self.input.len());
128 self.finished = false;
129 }
130
131 fn skip_ignored_controls(&mut self) {
132 while self
133 .input
134 .get(self.position)
135 .is_some_and(|byte| is_ignored_control(*byte))
136 {
137 self.position += 1;
138 }
139 }
140
141 fn token_bytes(&self, start: usize, end: usize) -> Cow<'a, [u8]> {
142 let bytes = &self.input[start..end];
143 if bytes.iter().any(|byte| is_ignored_control(*byte)) {
144 Cow::Owned(
145 bytes
146 .iter()
147 .copied()
148 .filter(|byte| !is_ignored_control(*byte))
149 .collect(),
150 )
151 } else {
152 Cow::Borrowed(bytes)
153 }
154 }
155
156 fn match_ignoring_controls(&self, start: usize, expected: &[u8]) -> Option<usize> {
157 let mut position = start;
158 for &expected_byte in expected {
159 while self
160 .input
161 .get(position)
162 .is_some_and(|byte| is_ignored_control(*byte))
163 {
164 position += 1;
165 }
166 if self.input.get(position) != Some(&expected_byte) {
167 return None;
168 }
169 position += 1;
170 }
171 Some(position)
172 }
173
174 fn match_ignoring_text_controls(&self, start: usize, expected: &[u8]) -> Option<usize> {
175 let mut position = start;
176 for &expected_byte in expected {
177 loop {
178 while self
179 .input
180 .get(position)
181 .is_some_and(|byte| is_ignored_control(*byte))
182 {
183 position += 1;
184 }
185 let Some(end) = self
186 .match_ignoring_controls(position, b"\\N\\")
187 .or_else(|| self.match_ignoring_controls(position, b"\\F\\"))
188 else {
189 break;
190 };
191 position = end;
192 }
193 if self.input.get(position) != Some(&expected_byte) {
194 return None;
195 }
196 position += 1;
197 }
198 Some(position)
199 }
200
201 pub fn next_spanned(&mut self) -> Result<Option<Spanned<Token<'a>>>, StepError> {
207 if self.finished {
208 return Ok(None);
209 }
210 self.skip_trivia()?;
211 let Some(&byte) = self.input.get(self.position) else {
212 self.finished = true;
213 return Ok(None);
214 };
215 let start = self.position;
216 let value = match byte {
217 b'(' => self.single(Token::OpenParen),
218 b')' => self.single(Token::CloseParen),
219 b',' => self.single(Token::Comma),
220 b'=' => self.single(Token::Equals),
221 b';' => self.single(Token::Semicolon),
222 b'$' => self.single(Token::Dollar),
223 b'*' => self.single(Token::Star),
224 b'#' => self.lex_id(start)?,
225 b'!' => self.lex_user_defined_name(start)?,
226 b'\'' => self.lex_text(start)?,
227 b'"' => self.lex_binary(start)?,
228 b'.' if self.input.get(start + 1).is_some_and(u8::is_ascii_digit) => {
229 self.lex_number(start)?
230 }
231 b'.' => self.lex_keyword(start)?,
232 b'0'..=b'9' | b'-' | b'+' => self.lex_number(start)?,
233 value if value.is_ascii_alphabetic() || value == b'_' => self.lex_name(),
234 _ => {
235 self.position += 1;
236 return Err(StepError::syntax(
237 Span::new(start, self.position),
238 format!("unexpected byte 0x{byte:02X}"),
239 ));
240 }
241 };
242 Ok(Some(Spanned::new(value, Span::new(start, self.position))))
243 }
244
245 pub fn next_token(&mut self) -> Result<Option<Spanned<Token<'a>>>, StepError> {
253 self.next_spanned()
254 }
255
256 fn skip_trivia(&mut self) -> Result<(), StepError> {
257 if self.position == 0 && self.input.starts_with(&[0xef, 0xbb, 0xbf]) {
258 self.position = 3;
259 }
260 loop {
261 while self
262 .input
263 .get(self.position)
264 .is_some_and(u8::is_ascii_whitespace)
265 {
266 self.position += 1;
267 }
268 if let Some(end) = self
269 .match_ignoring_controls(self.position, b"\\N\\")
270 .or_else(|| self.match_ignoring_controls(self.position, b"\\F\\"))
271 {
272 self.position = end;
273 continue;
274 }
275 if let Some(body_start) = self.match_ignoring_controls(self.position, b"/*") {
276 let start = self.position;
277 let mut cursor = body_start;
278 loop {
279 if let Some(end) = self.match_ignoring_controls(cursor, b"*/") {
280 self.position = end;
281 break;
282 }
283 if cursor >= self.input.len() {
284 self.position = self.input.len();
285 return Err(StepError::syntax(
286 Span::new(start, self.position),
287 "unterminated comment",
288 ));
289 }
290 cursor += 1;
291 }
292 continue;
293 }
294 return Ok(());
295 }
296 }
297
298 fn single(&mut self, token: Token<'a>) -> Token<'a> {
299 self.position += 1;
300 token
301 }
302
303 fn lex_id(&mut self, start: usize) -> Result<Token<'a>, StepError> {
304 self.position += 1;
305 self.skip_ignored_controls();
306 let digits = self.position;
307 let mut saw_digit = false;
308 while let Some(&byte) = self.input.get(self.position) {
309 if is_ignored_control(byte) {
310 self.position += 1;
311 } else if byte.is_ascii_digit() {
312 saw_digit = true;
313 self.position += 1;
314 } else {
315 break;
316 }
317 }
318 if !saw_digit {
319 return Err(StepError::syntax(
320 Span::new(start, self.position),
321 "expected digits after '#'",
322 ));
323 }
324 Ok(Token::Id(self.token_bytes(digits, self.position)))
325 }
326
327 fn lex_text(&mut self, start: usize) -> Result<Token<'a>, StepError> {
328 self.position += 1;
329 let body_start = self.position;
330 while let Some(&byte) = self.input.get(self.position) {
331 if byte == b'\'' {
332 if let Some(end) = self.match_ignoring_text_controls(self.position, b"''") {
333 self.position = end;
334 continue;
335 }
336 let text = strip_text_print_directives(self.token_bytes(body_start, self.position));
337 self.position += 1;
338 return Ok(Token::Text(text));
339 }
340 self.position += 1;
341 }
342 Err(StepError::syntax(
343 Span::new(start, self.position),
344 "unterminated string literal",
345 ))
346 }
347
348 fn lex_binary(&mut self, start: usize) -> Result<Token<'a>, StepError> {
349 self.position += 1;
350 let body_start = self.position;
351 while let Some(&byte) = self.input.get(self.position) {
352 if byte == b'"' {
353 let body = strip_print_directives(self.token_bytes(body_start, self.position));
354 self.position += 1;
355 if !is_valid_binary(body.as_ref()) {
356 return Err(StepError::syntax(
357 Span::new(start, self.position),
358 "invalid binary literal",
359 ));
360 }
361 return Ok(Token::Binary(body));
362 }
363 self.position += 1;
364 }
365 Err(StepError::syntax(
366 Span::new(start, self.position),
367 "unterminated binary literal",
368 ))
369 }
370
371 fn lex_keyword(&mut self, start: usize) -> Result<Token<'a>, StepError> {
372 self.position += 1;
373 let body_start = self.position;
374 while let Some(&byte) = self.input.get(self.position) {
375 if is_ignored_control(byte) {
376 self.position += 1;
377 continue;
378 }
379 if byte == b'.' {
380 let body = self.token_bytes(body_start, self.position);
381 if body.is_empty() {
382 self.position += 1;
383 return Err(StepError::syntax(
384 Span::new(start, self.position),
385 "empty dotted keyword",
386 ));
387 }
388 self.position += 1;
389 return Ok(Token::Keyword(body));
390 }
391 if !(byte.is_ascii_alphanumeric() || byte == b'_') {
392 break;
393 }
394 self.position += 1;
395 }
396 Err(StepError::syntax(
397 Span::new(start, self.position),
398 "unterminated dotted keyword",
399 ))
400 }
401
402 fn lex_name(&mut self) -> Token<'a> {
403 let start = self.position;
404 while let Some(&byte) = self.input.get(self.position) {
405 if is_ignored_control(byte)
406 || byte.is_ascii_alphanumeric()
407 || matches!(byte, b'_' | b'-')
408 {
409 self.position += 1;
410 } else {
411 break;
412 }
413 }
414 Token::Name(self.token_bytes(start, self.position))
415 }
416
417 fn lex_user_defined_name(&mut self, start: usize) -> Result<Token<'a>, StepError> {
418 self.position += 1;
419 self.skip_ignored_controls();
420 let body_start = self.position;
421 if !self
422 .input
423 .get(body_start)
424 .is_some_and(|byte| byte.is_ascii_alphabetic() || *byte == b'_')
425 {
426 return Err(StepError::syntax(
427 Span::new(start, self.position),
428 "invalid user-defined keyword",
429 ));
430 }
431 self.position += 1;
432 while let Some(&byte) = self.input.get(self.position) {
433 if is_ignored_control(byte) || byte.is_ascii_alphanumeric() || byte == b'_' {
434 self.position += 1;
435 } else {
436 break;
437 }
438 }
439 if matches!(self.input.get(self.position), Some(b'-')) {
440 return Err(StepError::syntax(
441 Span::new(start, self.position + 1),
442 "invalid user-defined keyword",
443 ));
444 }
445 Ok(Token::Name(self.token_bytes(start, self.position)))
446 }
447
448 fn lex_number(&mut self, start: usize) -> Result<Token<'a>, StepError> {
449 if matches!(self.input.get(self.position), Some(b'+' | b'-')) {
450 self.position += 1;
451 self.skip_ignored_controls();
452 }
453 let mut integer_digits = 0;
454 loop {
455 self.skip_ignored_controls();
456 if self
457 .input
458 .get(self.position)
459 .is_some_and(u8::is_ascii_digit)
460 {
461 integer_digits += 1;
462 self.position += 1;
463 } else {
464 break;
465 }
466 }
467 if integer_digits == 0 {
468 return Err(StepError::syntax(
469 Span::new(start, self.position),
470 "number has no leading digits",
471 ));
472 }
473 self.skip_ignored_controls();
474 let mut real = false;
475 if self.input.get(self.position) == Some(&b'.') {
476 real = true;
477 self.position += 1;
478 loop {
479 self.skip_ignored_controls();
480 if self
481 .input
482 .get(self.position)
483 .is_some_and(u8::is_ascii_digit)
484 {
485 self.position += 1;
486 } else {
487 break;
488 }
489 }
490 }
491 self.skip_ignored_controls();
492 if matches!(self.input.get(self.position), Some(b'e' | b'E')) {
493 if !real {
494 return Err(StepError::syntax(
495 Span::new(start, self.position + 1),
496 "real requires a decimal point before its exponent",
497 ));
498 }
499 self.position += 1;
500 self.skip_ignored_controls();
501 if matches!(self.input.get(self.position), Some(b'+' | b'-')) {
502 self.position += 1;
503 self.skip_ignored_controls();
504 }
505 let mut exponent_digits = 0;
506 loop {
507 self.skip_ignored_controls();
508 if self
509 .input
510 .get(self.position)
511 .is_some_and(u8::is_ascii_digit)
512 {
513 exponent_digits += 1;
514 self.position += 1;
515 } else {
516 break;
517 }
518 }
519 if exponent_digits == 0 {
520 return Err(StepError::syntax(
521 Span::new(start, self.position),
522 "real exponent has no digits",
523 ));
524 }
525 }
526 let text = self.token_bytes(start, self.position);
527 Ok(if real {
528 Token::Real(text)
529 } else {
530 Token::Integer(text)
531 })
532 }
533}
534
535impl<'a> Iterator for Lexer<'a> {
536 type Item = Result<Spanned<Token<'a>>, StepError>;
537
538 fn next(&mut self) -> Option<Self::Item> {
539 match self.next_spanned() {
540 Ok(Some(token)) => Some(Ok(token)),
541 Ok(None) => None,
542 Err(error) => {
543 self.finished = true;
544 Some(Err(error))
545 }
546 }
547 }
548}