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());
106
107 let mut safe_end = end;
108 while safe_end > self.pos && !self.input.is_char_boundary(safe_end) {
109 safe_end -= 1;
110 }
111
112 &self.input[self.pos..safe_end]
113 }
114
115 fn advance(&mut self) -> Option<char> {
116 if let Some(ch) = self.current_char() {
117 self.pos += ch.len_utf8();
118 if ch == '\n' {
119 self.line += 1;
120 self.column = 1;
121 self.line_start_pos = self.pos;
122 } else {
123 self.column += 1;
124 }
125 Some(ch)
126 } else {
127 None
128 }
129 }
130
131 fn skip_whitespace(&mut self) {
132 while let Some(ch) = self.peek_char() {
133 if ch.is_whitespace() && ch != '\n' {
134 self.advance();
135 } else {
136 break;
137 }
138 }
139 }
140
141 fn skip_line(&mut self) {
142 while let Some(ch) = self.peek_char() {
143 if ch == '\n' {
144 self.advance();
145 break;
146 }
147 self.advance();
148 }
149 }
150
151 fn is_at_end(&self) -> bool {
152 self.pos >= self.input.len()
153 }
154
155 fn error(&self, message: impl Into<String>) -> ParseError {
156 let line_end = self.input[self.line_start_pos..]
157 .find('\n')
158 .map(|i| self.line_start_pos + i)
159 .unwrap_or(self.input.len());
160 let line_text = &self.input[self.line_start_pos..line_end];
161
162 ParseError {
163 message: message.into(),
164 line: self.line,
165 column: self.column,
166 input: LocatedSpan::new(self.line_start_pos, self.column, self.line, line_text.to_string()),
167 code: format!("{:?}", line_text),
168 }
169 }
170
171 fn parse_blocks(&mut self) -> Result<Vec<Block>, ParseError> {
172 let mut blocks = Vec::new();
173
174 while !self.is_at_end() {
175 if let Some(block) = self.parse_block()? {
176 blocks.push(block);
177 }
178 }
179
180 Ok(blocks)
181 }
182
183 fn parse_block(&mut self) -> Result<Option<Block>, ParseError> {
184 let line_number = self.line;
185 let literal_start = self.pos;
186
187 let commands = self.parse_commands()?;
188
189 let literal_end = self.pos;
190 let literal = self.input[literal_start..literal_end].to_string();
191
192 if self.is_at_end() && commands.is_empty() {
193 return Ok(Some(Block {
194 literal,
195 commands,
196 line_number,
197 }));
198 }
199
200 if commands.is_empty() {
201 return Ok(None);
202 }
203
204 if !self.parse_separator()? {
205 return Err(self.error("Expected --- separator"));
206 }
207
208 self.parse_output()?;
209
210 Ok(Some(Block {
211 literal,
212 commands,
213 line_number,
214 }))
215 }
216
217 fn parse_commands(&mut self) -> Result<Vec<Command>, ParseError> {
218 let mut commands = Vec::new();
219
220 loop {
221 if self.skip_empty_or_comment_line() {
222 continue;
223 }
224
225 if self.is_at_end() {
226 break;
227 }
228
229 if self.peek_str(3) == "---" && !commands.is_empty() {
230 break;
231 }
232
233 if let Some(ch) = self.peek_char()
234 && ch.is_whitespace() && ch != '\n'
235 {
236 return Err(self.error("Command cannot start with whitespace"));
237 }
238
239 match self.parse_command() {
240 Ok(cmd) => commands.push(cmd),
241 Err(e) => {
242 if self.peek_str(3) == "---" && commands.is_empty() {
243 return Err(e);
244 }
245 return Err(e);
246 }
247 }
248 }
249
250 Ok(commands)
251 }
252
253 fn parse_command(&mut self) -> Result<Command, ParseError> {
254 let line_number = self.line;
255
256 let silent = if self.peek_char() == Some('(') {
257 self.advance();
258 self.skip_whitespace();
259 true
260 } else {
261 false
262 };
263
264 let mut tags = HashSet::new();
265 let mut prefix = None;
266
267 let saved_pos = self.pos;
268 self.skip_whitespace();
269 if let Ok(s) = self.parse_string() {
270 self.skip_whitespace();
271 if self.peek_char() == Some(':') {
272 self.advance();
273 self.skip_whitespace();
274 prefix = Some(s);
275 } else {
276 self.pos = saved_pos;
277 }
278 }
279
280 self.skip_whitespace();
281 if let Some(parsed_tags) = self.parse_taglist()? {
282 tags.extend(parsed_tags);
283 }
284 self.skip_whitespace();
285
286 let fail = if self.peek_char() == Some('!') {
287 self.advance();
288 self.skip_whitespace();
289 true
290 } else {
291 false
292 };
293
294 if self.peek_char() == Some('>') {
295 self.advance();
296 self.skip_whitespace();
297
298 let fail = if !fail && self.peek_char() == Some('!') {
299 self.advance();
300 self.skip_whitespace();
301 true
302 } else {
303 fail
304 };
305 let name = self.parse_line_continuation()?;
306 return Ok(Command {
307 name,
308 args: Vec::new(),
309 tags,
310 prefix,
311 silent,
312 fail,
313 line_number,
314 });
315 }
316
317 self.skip_whitespace();
318 let name = self.parse_string().map_err(|_| self.error("Expected command name"))?;
319
320 let mut args = Vec::new();
321 loop {
322 self.skip_whitespace();
323 if self.peek_char() == Some('[')
324 && let Some(parsed_tags) = self.parse_taglist()?
325 {
326 tags.extend(parsed_tags);
327 break;
328 }
329
330 if silent && self.peek_char() == Some(')') {
331 break;
332 }
333
334 if self.peek_char() == Some('#') || self.peek_str(2) == "//" {
335 break;
336 }
337
338 if self.peek_char() == Some('\n') || self.is_at_end() {
339 break;
340 }
341
342 let saved_pos = self.pos;
343 let saved_line = self.line;
344 let saved_column = self.column;
345 let saved_line_start = self.line_start_pos;
346 match self.parse_argument() {
347 Ok(arg) => args.push(arg),
348 Err(_) => {
349 self.pos = saved_pos;
350 self.line = saved_line;
351 self.column = saved_column;
352 self.line_start_pos = saved_line_start;
353 break;
354 }
355 }
356 }
357
358 if silent {
359 self.skip_whitespace();
360 if self.peek_char() != Some(')') {
361 return Err(self.error("Expected closing ) for silent command"));
362 }
363 self.advance();
364 }
365
366 self.skip_whitespace();
367 if self.peek_char() == Some('#') || self.peek_str(2) == "//" {
368 self.skip_line();
369 } else if self.peek_char() == Some('\n') {
370 self.advance();
371 } else if !self.is_at_end() {
372 return Err(self.error("Expected end of line"));
373 }
374
375 Ok(Command {
376 name,
377 args,
378 tags,
379 prefix,
380 silent,
381 fail,
382 line_number,
383 })
384 }
385
386 fn parse_argument(&mut self) -> Result<Argument, ParseError> {
387 let saved_pos = self.pos;
388 let saved_line = self.line;
389 let saved_column = self.column;
390 let saved_line_start = self.line_start_pos;
391
392 self.skip_whitespace();
393 if let Ok(key) = self.parse_string() {
394 if self.peek_char() == Some('=') {
395 self.advance();
396
397 let value = if matches!(self.peek_char(), Some(ch) if ch.is_whitespace())
398 || matches!(self.peek_char(), Some('[' | ')' | '#'))
399 || self.peek_char().is_none() || self.peek_str(2) == "//"
400 {
401 String::new()
402 } else {
403 match self.parse_string() {
404 Ok(v) => v,
405 Err(_) => {
406 let check_pos = self.pos;
407 let check_line = self.line;
408 let check_column = self.column;
409 let check_line_start = self.line_start_pos;
410
411 if self.parse_string().is_ok() {
412 if self.peek_char() == Some('=') {
413 self.pos = check_pos;
414 self.line = check_line;
415 self.column = check_column;
416 self.line_start_pos = check_line_start;
417 String::new()
418 } else {
419 self.pos = saved_pos;
420 self.line = saved_line;
421 self.column = saved_column;
422 self.line_start_pos = saved_line_start;
423 return Err(self.error(
424 "Expected argument value after =",
425 ));
426 }
427 } else {
428 self.pos = saved_pos;
429 self.line = saved_line;
430 self.column = saved_column;
431 self.line_start_pos = saved_line_start;
432 return Err(
433 self.error("Expected argument value after =")
434 );
435 }
436 }
437 }
438 };
439 return Ok(Argument {
440 key: Some(key),
441 value,
442 });
443 }
444
445 return Ok(Argument {
446 key: None,
447 value: key,
448 });
449 }
450
451 self.pos = saved_pos;
452 Err(self.error("Expected argument"))
453 }
454
455 fn parse_taglist(&mut self) -> Result<Option<HashSet<String>>, ParseError> {
456 if self.peek_char() != Some('[') {
457 return Ok(None);
458 }
459
460 self.advance();
461 let mut tags = HashSet::new();
462
463 loop {
464 self.skip_whitespace();
465
466 if self.peek_char() == Some(']') {
467 if tags.is_empty() {
468 return Err(self.error("Empty tag list"));
469 }
470 self.advance();
471 break;
472 }
473
474 self.skip_whitespace();
475 let tag = self.parse_string().map_err(|_| self.error("Expected tag name"))?;
476 tags.insert(tag);
477
478 self.skip_whitespace();
479 if self.peek_char() == Some(',') {
480 self.advance();
481 self.skip_whitespace();
482 } else if self.peek_char() == Some(' ') {
483 self.skip_whitespace();
484 }
485 }
486
487 Ok(Some(tags))
488 }
489
490 fn parse_string(&mut self) -> Result<String, ParseError> {
491 match self.peek_char() {
492 Some('\'') => self.parse_quoted_string('\''),
493 Some('"') => self.parse_quoted_string('"'),
494 _ => self.parse_unquoted_string(),
495 }
496 }
497
498 fn parse_unquoted_string(&mut self) -> Result<String, ParseError> {
499 let mut result = String::new();
500
501 match self.peek_char() {
502 Some(ch) if ch.is_alphanumeric() || ch == '_' => {
503 result.push(ch);
504 self.advance();
505 }
506 _ => return Err(self.error("Expected string")),
507 }
508
509 while let Some(ch) = self.peek_char() {
510 if ch.is_alphanumeric() || "_-./@".contains(ch) {
511 result.push(ch);
512 self.advance();
513 } else {
514 break;
515 }
516 }
517
518 Ok(result)
519 }
520
521 fn parse_quoted_string(&mut self, quote: char) -> Result<String, ParseError> {
522 let mut result = String::new();
523
524 if self.peek_char() != Some(quote) {
525 return Err(self.error(format!("Expected {} quote", quote)));
526 }
527 self.advance();
528
529 while let Some(ch) = self.peek_char() {
530 if ch == quote {
531 self.advance();
532 return Ok(result);
533 } else if ch == '\\' {
534 self.advance();
535 match self.peek_char() {
536 Some('\'') => {
537 result.push('\'');
538 self.advance();
539 }
540 Some('"') => {
541 result.push('"');
542 self.advance();
543 }
544 Some('\\') => {
545 result.push('\\');
546 self.advance();
547 }
548 Some('0') => {
549 result.push('\0');
550 self.advance();
551 }
552 Some('n') => {
553 result.push('\n');
554 self.advance();
555 }
556 Some('r') => {
557 result.push('\r');
558 self.advance();
559 }
560 Some('t') => {
561 result.push('\t');
562 self.advance();
563 }
564 Some('x') => {
565 self.advance();
566 let hex = self.parse_hex_digits(2, 2)?;
567 let byte = u8::from_str_radix(&hex, 16)
568 .map_err(|_| self.error("Invalid hex escape"))?;
569 result.push(char::from(byte));
570 }
571 Some('u') => {
572 self.advance();
573 if self.peek_char() != Some('{') {
574 return Err(self.error("Expected { after \\u"));
575 }
576 self.advance();
577 let hex = self.parse_hex_digits(1, 6)?;
578 if self.peek_char() != Some('}') {
579 return Err(self.error("Expected } after unicode escape"));
580 }
581 self.advance();
582 let codepoint = u32::from_str_radix(&hex, 16)
583 .map_err(|_| self.error("Invalid unicode escape"))?;
584 let ch = char::from_u32(codepoint)
585 .ok_or_else(|| self.error("Invalid unicode codepoint"))?;
586 result.push(ch);
587 }
588 _ => {
589 return Err(self.error("Invalid escape sequence"));
590 }
591 }
592 } else {
593 result.push(ch);
594 self.advance();
595 }
596 }
597
598 Err(self.error(format!("Unterminated string (missing {})", quote)))
599 }
600
601 fn parse_hex_digits(&mut self, min: usize, max: usize) -> Result<String, ParseError> {
602 let mut hex = String::new();
603 for i in 0..max {
604 match self.peek_char() {
605 Some(ch) if ch.is_ascii_hexdigit() => {
606 hex.push(ch);
607 self.advance();
608 }
609 _ => {
610 if i < min {
611 return Err(self.error(format!("Expected at least {} hex digits", min)));
612 }
613 break;
614 }
615 }
616 }
617 if hex.len() < min {
618 return Err(self.error(format!("Expected at least {} hex digits", min)));
619 }
620 Ok(hex)
621 }
622
623 fn skip_empty_or_comment_line(&mut self) -> bool {
624 let saved_pos = self.pos;
625
626 self.skip_whitespace();
627
628 if self.peek_char() == Some('#') || self.peek_str(2) == "//" {
629 self.skip_line();
630 return true;
631 }
632
633 if self.peek_char() == Some('\n') {
634 self.advance();
635 return true;
636 }
637
638 self.pos = saved_pos;
639 false
640 }
641
642 fn parse_separator(&mut self) -> Result<bool, ParseError> {
643 if self.peek_str(3) != "---" {
644 return Ok(false);
645 }
646
647 self.advance();
648 self.advance();
649 self.advance();
650
651 match self.peek_char() {
652 Some('\r') => {
653 self.advance();
654 if self.peek_char() == Some('\n') {
655 self.advance();
656 }
657 Ok(true)
658 }
659 Some('\n') => {
660 self.advance();
661 Ok(true)
662 }
663 None => Ok(true),
664 _ => Err(self.error("Separator must be followed by newline or EOF")),
665 }
666 }
667
668 fn parse_output(&mut self) -> Result<(), ParseError> {
669 if self.peek_char() == Some('\n') || self.is_at_end() {
670 if self.peek_char() == Some('\n') {
671 self.advance();
672 }
673 return Ok(());
674 }
675
676 let mut last_was_newline = false;
677 while !self.is_at_end() {
678 let ch = self.advance().unwrap();
679 if ch == '\n' {
680 if last_was_newline {
681 break;
682 }
683 last_was_newline = true;
684 } else {
685 last_was_newline = false;
686 }
687 }
688
689 Ok(())
690 }
691
692 fn parse_line_continuation(&mut self) -> Result<String, ParseError> {
693 let mut result = String::new();
694
695 loop {
696 while let Some(ch) = self.peek_char() {
697 if ch == '\n' {
698 break;
699 }
700 result.push(ch);
701 self.advance();
702 }
703
704 if result.ends_with('\\') {
705 result.pop();
706 if self.peek_char() == Some('\n') {
707 self.advance();
708 continue;
709 }
710 }
711
712 if self.peek_char() == Some('\n') {
713 self.advance();
714 }
715
716 break;
717 }
718
719 Ok(result)
720 }
721}