reifydb_testing/testscript/
parser.rs1use std::{collections::HashSet, error, fmt};
13
14use crate::testscript::command::{Argument, Block, Command};
15
16#[derive(Debug, Clone)]
17pub struct ParseError {
18 pub message: String,
19 pub line: u32,
20 pub column: usize,
21 pub input: LocatedSpan,
22 pub code: String,
23}
24
25impl fmt::Display for ParseError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "Parse error at line {}:{}: {}", self.line, self.column, self.message)
28 }
29}
30
31impl error::Error for ParseError {}
32
33#[derive(Debug, Clone)]
34pub struct LocatedSpan {
35 column: usize,
36 line: u32,
37 line_text: String,
38}
39
40impl LocatedSpan {
41 fn new(_line_start: usize, column: usize, line: u32, line_text: String) -> Self {
42 LocatedSpan {
43 column,
44 line,
45 line_text,
46 }
47 }
48
49 pub fn location_line(&self) -> u32 {
50 self.line
51 }
52
53 pub fn get_column(&self) -> usize {
54 self.column
55 }
56
57 pub fn get_utf8_column(&self) -> usize {
58 self.column
59 }
60
61 pub fn get_line_beginning(&self) -> &[u8] {
62 self.line_text.as_bytes()
63 }
64}
65
66pub(crate) fn parse(input: &str) -> Result<Vec<Block>, ParseError> {
67 let mut parser = Parser::new(input);
68 parser.parse_blocks()
69}
70
71#[cfg(test)]
72pub(crate) fn parse_command(input: &str) -> Result<Command, ParseError> {
73 let mut parser = Parser::new(input);
74 parser.parse_command()
75}
76
77struct Parser<'a> {
78 input: &'a str,
79 pos: usize,
80 line: u32,
81 column: usize,
82 line_start_pos: usize,
83}
84
85impl<'a> Parser<'a> {
86 fn new(input: &'a str) -> Self {
87 Parser {
88 input,
89 pos: 0,
90 line: 1,
91 column: 1,
92 line_start_pos: 0,
93 }
94 }
95
96 fn current_char(&self) -> Option<char> {
97 self.input[self.pos..].chars().next()
98 }
99
100 fn peek_char(&self) -> Option<char> {
101 self.current_char()
102 }
103
104 fn peek_str(&self, n: usize) -> &str {
105 let end = (self.pos + n).min(self.input.len());
109
110 let mut safe_end = end;
112 while safe_end > self.pos && !self.input.is_char_boundary(safe_end) {
113 safe_end -= 1;
114 }
115
116 &self.input[self.pos..safe_end]
117 }
118
119 fn advance(&mut self) -> Option<char> {
120 if let Some(ch) = self.current_char() {
121 self.pos += ch.len_utf8();
122 if ch == '\n' {
123 self.line += 1;
124 self.column = 1;
125 self.line_start_pos = self.pos;
126 } else {
127 self.column += 1;
128 }
129 Some(ch)
130 } else {
131 None
132 }
133 }
134
135 fn skip_whitespace(&mut self) {
136 while let Some(ch) = self.peek_char() {
137 if ch.is_whitespace() && ch != '\n' {
138 self.advance();
139 } else {
140 break;
141 }
142 }
143 }
144
145 fn skip_line(&mut self) {
146 while let Some(ch) = self.peek_char() {
147 if ch == '\n' {
148 self.advance();
149 break;
150 }
151 self.advance();
152 }
153 }
154
155 fn is_at_end(&self) -> bool {
156 self.pos >= self.input.len()
157 }
158
159 fn error(&self, message: impl Into<String>) -> ParseError {
160 let line_end = self.input[self.line_start_pos..]
161 .find('\n')
162 .map(|i| self.line_start_pos + i)
163 .unwrap_or(self.input.len());
164 let line_text = &self.input[self.line_start_pos..line_end];
165
166 ParseError {
167 message: message.into(),
168 line: self.line,
169 column: self.column,
170 input: LocatedSpan::new(self.line_start_pos, self.column, self.line, line_text.to_string()),
171 code: format!("{:?}", line_text),
172 }
173 }
174
175 fn parse_blocks(&mut self) -> Result<Vec<Block>, ParseError> {
176 let mut blocks = Vec::new();
177
178 while !self.is_at_end() {
179 if let Some(block) = self.parse_block()? {
180 blocks.push(block);
181 }
182 }
183
184 Ok(blocks)
185 }
186
187 fn parse_block(&mut self) -> Result<Option<Block>, ParseError> {
188 let line_number = self.line;
189 let literal_start = self.pos;
190
191 let commands = self.parse_commands()?;
193
194 let literal_end = self.pos;
196 let literal = self.input[literal_start..literal_end].to_string();
197
198 if self.is_at_end() && commands.is_empty() {
200 return Ok(Some(Block {
201 literal,
202 commands,
203 line_number,
204 }));
205 }
206
207 if commands.is_empty() {
209 return Ok(None);
210 }
211
212 if !self.parse_separator()? {
214 return Err(self.error("Expected --- separator"));
215 }
216
217 self.parse_output()?;
219
220 Ok(Some(Block {
221 literal,
222 commands,
223 line_number,
224 }))
225 }
226
227 fn parse_commands(&mut self) -> Result<Vec<Command>, ParseError> {
228 let mut commands = Vec::new();
229
230 loop {
231 if self.skip_empty_or_comment_line() {
233 continue;
234 }
235
236 if self.is_at_end() {
238 break;
239 }
240
241 if self.peek_str(3) == "---" && !commands.is_empty() {
243 break;
244 }
245
246 if let Some(ch) = self.peek_char()
249 && ch.is_whitespace() && ch != '\n'
250 {
251 return Err(self.error("Command cannot start with whitespace"));
252 }
253
254 match self.parse_command() {
256 Ok(cmd) => commands.push(cmd),
257 Err(e) => {
258 if self.peek_str(3) == "---" && commands.is_empty() {
261 return Err(e);
262 }
263 return Err(e);
264 }
265 }
266 }
267
268 Ok(commands)
269 }
270
271 fn parse_command(&mut self) -> Result<Command, ParseError> {
272 let line_number = self.line;
273
274 let silent = if self.peek_char() == Some('(') {
276 self.advance();
277 self.skip_whitespace();
278 true
279 } else {
280 false
281 };
282
283 let mut tags = HashSet::new();
285 let mut prefix = None;
286
287 let saved_pos = self.pos;
289 self.skip_whitespace();
290 if let Ok(s) = self.parse_string() {
291 self.skip_whitespace();
292 if self.peek_char() == Some(':') {
293 self.advance();
294 self.skip_whitespace();
295 prefix = Some(s);
296 } else {
297 self.pos = saved_pos;
299 }
300 }
301
302 self.skip_whitespace();
304 if let Some(parsed_tags) = self.parse_taglist()? {
305 tags.extend(parsed_tags);
306 }
307 self.skip_whitespace();
308
309 let fail = if self.peek_char() == Some('!') {
311 self.advance();
312 self.skip_whitespace();
313 true
314 } else {
315 false
316 };
317
318 if self.peek_char() == Some('>') {
320 self.advance();
321 self.skip_whitespace();
322 let fail = if !fail && self.peek_char() == Some('!') {
324 self.advance();
325 self.skip_whitespace();
326 true
327 } else {
328 fail
329 };
330 let name = self.parse_line_continuation()?;
331 return Ok(Command {
332 name,
333 args: Vec::new(),
334 tags,
335 prefix,
336 silent,
337 fail,
338 line_number,
339 });
340 }
341
342 self.skip_whitespace();
344 let name = self.parse_string().map_err(|_| self.error("Expected command name"))?;
345
346 let mut args = Vec::new();
348 loop {
349 self.skip_whitespace();
350 if self.peek_char() == Some('[') {
351 if let Some(parsed_tags) = self.parse_taglist()? {
353 tags.extend(parsed_tags);
354 break;
355 }
356 }
357
358 if silent && self.peek_char() == Some(')') {
360 break;
361 }
362
363 if self.peek_char() == Some('#') || self.peek_str(2) == "//" {
364 break;
365 }
366
367 if self.peek_char() == Some('\n') || self.is_at_end() {
368 break;
369 }
370
371 let saved_pos = self.pos;
373 let saved_line = self.line;
374 let saved_column = self.column;
375 let saved_line_start = self.line_start_pos;
376 match self.parse_argument() {
377 Ok(arg) => args.push(arg),
378 Err(_) => {
379 self.pos = saved_pos;
380 self.line = saved_line;
381 self.column = saved_column;
382 self.line_start_pos = saved_line_start;
383 break;
384 }
385 }
386 }
387
388 if silent {
390 self.skip_whitespace();
391 if self.peek_char() != Some(')') {
392 return Err(self.error("Expected closing ) for silent command"));
393 }
394 self.advance();
395 }
396
397 self.skip_whitespace();
399 if self.peek_char() == Some('#') || self.peek_str(2) == "//" {
400 self.skip_line();
401 } else if self.peek_char() == Some('\n') {
402 self.advance();
403 } else if !self.is_at_end() {
404 return Err(self.error("Expected end of line"));
405 }
406
407 Ok(Command {
408 name,
409 args,
410 tags,
411 prefix,
412 silent,
413 fail,
414 line_number,
415 })
416 }
417
418 fn parse_argument(&mut self) -> Result<Argument, ParseError> {
419 let saved_pos = self.pos;
421 let saved_line = self.line;
422 let saved_column = self.column;
423 let saved_line_start = self.line_start_pos;
424
425 self.skip_whitespace();
426 if let Ok(key) = self.parse_string() {
427 if self.peek_char() == Some('=') {
428 self.advance();
429 let value = if matches!(self.peek_char(), Some(ch) if ch.is_whitespace())
433 || matches!(self.peek_char(), Some('[' | ')' | '#'))
434 || self.peek_char().is_none() || self.peek_str(2) == "//"
435 {
436 String::new()
438 } else {
439 match self.parse_string() {
441 Ok(v) => v,
442 Err(_) => {
443 let check_pos = self.pos;
449 let check_line = self.line;
450 let check_column = self.column;
451 let check_line_start = self.line_start_pos;
452
453 if self.parse_string().is_ok() {
457 if self.peek_char() == Some('=') {
458 self.pos = check_pos;
461 self.line = check_line;
462 self.column = check_column;
463 self.line_start_pos = check_line_start;
464 String::new()
465 } else {
466 self.pos = saved_pos;
468 self.line = saved_line;
469 self.column = saved_column;
470 self.line_start_pos = saved_line_start;
471 return Err(self.error(
472 "Expected argument value after =",
473 ));
474 }
475 } else {
476 self.pos = saved_pos;
480 self.line = saved_line;
481 self.column = saved_column;
482 self.line_start_pos = saved_line_start;
483 return Err(
484 self.error("Expected argument value after =")
485 );
486 }
487 }
488 }
489 };
490 return Ok(Argument {
491 key: Some(key),
492 value,
493 });
494 }
495 return Ok(Argument {
497 key: None,
498 value: key,
499 });
500 }
501
502 self.pos = saved_pos;
503 Err(self.error("Expected argument"))
504 }
505
506 fn parse_taglist(&mut self) -> Result<Option<HashSet<String>>, ParseError> {
507 if self.peek_char() != Some('[') {
508 return Ok(None);
509 }
510
511 self.advance();
512 let mut tags = HashSet::new();
513
514 loop {
515 self.skip_whitespace();
516
517 if self.peek_char() == Some(']') {
518 if tags.is_empty() {
520 return Err(self.error("Empty tag list"));
521 }
522 self.advance();
523 break;
524 }
525
526 self.skip_whitespace();
527 let tag = self.parse_string().map_err(|_| self.error("Expected tag name"))?;
528 tags.insert(tag);
529
530 self.skip_whitespace();
531 if self.peek_char() == Some(',') {
532 self.advance();
533 self.skip_whitespace();
534 } else if self.peek_char() == Some(' ') {
535 self.skip_whitespace();
536 }
537 }
538
539 Ok(Some(tags))
540 }
541
542 fn parse_string(&mut self) -> Result<String, ParseError> {
543 match self.peek_char() {
547 Some('\'') => self.parse_quoted_string('\''),
548 Some('"') => self.parse_quoted_string('"'),
549 _ => self.parse_unquoted_string(),
550 }
551 }
552
553 fn parse_unquoted_string(&mut self) -> Result<String, ParseError> {
554 let mut result = String::new();
555
556 match self.peek_char() {
558 Some(ch) if ch.is_alphanumeric() || ch == '_' => {
559 result.push(ch);
560 self.advance();
561 }
562 _ => return Err(self.error("Expected string")),
563 }
564
565 while let Some(ch) = self.peek_char() {
567 if ch.is_alphanumeric() || "_-./@".contains(ch) {
568 result.push(ch);
569 self.advance();
570 } else {
571 break;
572 }
573 }
574
575 Ok(result)
576 }
577
578 fn parse_quoted_string(&mut self, quote: char) -> Result<String, ParseError> {
579 let mut result = String::new();
580
581 if self.peek_char() != Some(quote) {
583 return Err(self.error(format!("Expected {} quote", quote)));
584 }
585 self.advance();
586
587 while let Some(ch) = self.peek_char() {
588 if ch == quote {
589 self.advance();
590 return Ok(result);
591 } else if ch == '\\' {
592 self.advance();
593 match self.peek_char() {
594 Some('\'') => {
595 result.push('\'');
596 self.advance();
597 }
598 Some('"') => {
599 result.push('"');
600 self.advance();
601 }
602 Some('\\') => {
603 result.push('\\');
604 self.advance();
605 }
606 Some('0') => {
607 result.push('\0');
608 self.advance();
609 }
610 Some('n') => {
611 result.push('\n');
612 self.advance();
613 }
614 Some('r') => {
615 result.push('\r');
616 self.advance();
617 }
618 Some('t') => {
619 result.push('\t');
620 self.advance();
621 }
622 Some('x') => {
623 self.advance();
624 let hex = self.parse_hex_digits(2, 2)?;
625 let byte = u8::from_str_radix(&hex, 16)
626 .map_err(|_| self.error("Invalid hex escape"))?;
627 result.push(char::from(byte));
628 }
629 Some('u') => {
630 self.advance();
631 if self.peek_char() != Some('{') {
632 return Err(self.error("Expected { after \\u"));
633 }
634 self.advance();
635 let hex = self.parse_hex_digits(1, 6)?;
636 if self.peek_char() != Some('}') {
637 return Err(self.error("Expected } after unicode escape"));
638 }
639 self.advance();
640 let codepoint = u32::from_str_radix(&hex, 16)
641 .map_err(|_| self.error("Invalid unicode escape"))?;
642 let ch = char::from_u32(codepoint)
643 .ok_or_else(|| self.error("Invalid unicode codepoint"))?;
644 result.push(ch);
645 }
646 _ => {
647 return Err(self.error("Invalid escape sequence"));
648 }
649 }
650 } else {
651 result.push(ch);
652 self.advance();
653 }
654 }
655
656 Err(self.error(format!("Unterminated string (missing {})", quote)))
657 }
658
659 fn parse_hex_digits(&mut self, min: usize, max: usize) -> Result<String, ParseError> {
660 let mut hex = String::new();
661 for i in 0..max {
662 match self.peek_char() {
663 Some(ch) if ch.is_ascii_hexdigit() => {
664 hex.push(ch);
665 self.advance();
666 }
667 _ => {
668 if i < min {
669 return Err(self.error(format!("Expected at least {} hex digits", min)));
670 }
671 break;
672 }
673 }
674 }
675 if hex.len() < min {
676 return Err(self.error(format!("Expected at least {} hex digits", min)));
677 }
678 Ok(hex)
679 }
680
681 fn skip_empty_or_comment_line(&mut self) -> bool {
682 let saved_pos = self.pos;
683
684 self.skip_whitespace();
685
686 if self.peek_char() == Some('#') || self.peek_str(2) == "//" {
688 self.skip_line();
689 return true;
690 }
691
692 if self.peek_char() == Some('\n') {
694 self.advance();
695 return true;
696 }
697
698 self.pos = saved_pos;
700 false
701 }
702
703 fn parse_separator(&mut self) -> Result<bool, ParseError> {
704 if self.peek_str(3) != "---" {
705 return Ok(false);
706 }
707
708 self.advance(); self.advance(); self.advance(); match self.peek_char() {
714 Some('\r') => {
715 self.advance();
716 if self.peek_char() == Some('\n') {
717 self.advance();
718 }
719 Ok(true)
720 }
721 Some('\n') => {
722 self.advance();
723 Ok(true)
724 }
725 None => Ok(true),
726 _ => Err(self.error("Separator must be followed by newline or EOF")),
727 }
728 }
729
730 fn parse_output(&mut self) -> Result<(), ParseError> {
731 if self.peek_char() == Some('\n') || self.is_at_end() {
733 if self.peek_char() == Some('\n') {
734 self.advance();
735 }
736 return Ok(());
737 }
738
739 let mut last_was_newline = false;
741 while !self.is_at_end() {
742 let ch = self.advance().unwrap();
743 if ch == '\n' {
744 if last_was_newline {
745 break;
746 }
747 last_was_newline = true;
748 } else {
749 last_was_newline = false;
750 }
751 }
752
753 Ok(())
754 }
755
756 fn parse_line_continuation(&mut self) -> Result<String, ParseError> {
757 let mut result = String::new();
758
759 loop {
760 while let Some(ch) = self.peek_char() {
762 if ch == '\n' {
763 break;
764 }
765 result.push(ch);
766 self.advance();
767 }
768
769 if result.ends_with('\\') {
771 result.pop(); if self.peek_char() == Some('\n') {
773 self.advance(); continue;
775 }
776 }
777
778 if self.peek_char() == Some('\n') {
780 self.advance();
781 }
782
783 break;
784 }
785
786 Ok(result)
787 }
788}