Skip to main content

oni_comb_parser/text/
escaped.rs

1use alloc::string::String;
2
3use crate::error::{ExpectError, Expected, ParseError};
4use crate::fail::{Fail, PResult};
5use crate::input::Input;
6use crate::parser::Parser;
7use crate::str_input::StrInput;
8
9pub struct Escaped<F> {
10  open: char,
11  close: char,
12  escape: char,
13  handler: F,
14}
15
16pub fn escaped<F>(open: char, close: char, escape: char, handler: F) -> Escaped<F>
17where
18  F: FnMut(char) -> Option<char>, {
19  Escaped {
20    open,
21    close,
22    escape,
23    handler,
24  }
25}
26
27impl<'a, F> Parser<StrInput<'a>> for Escaped<F>
28where
29  F: FnMut(char) -> Option<char>,
30{
31  type Error = ParseError;
32  type Output = String;
33
34  #[inline]
35  fn parse_next(&mut self, input: &mut StrInput<'a>) -> PResult<String, ParseError> {
36    let pos = input.offset();
37    let remaining = input.as_str();
38    let mut chars = remaining.chars();
39
40    // opening delimiter
41    match chars.next() {
42      Some(c) if c == self.open => {}
43      _ => {
44        return Err(Fail::Backtrack(ParseError::from_expected(
45          pos,
46          Expected::Char(self.open),
47        )));
48      }
49    }
50
51    let mut result = String::new();
52    let mut consumed = self.open.len_utf8();
53
54    loop {
55      match chars.next() {
56        Some(c) if c == self.close => {
57          consumed += c.len_utf8();
58          input.advance(consumed);
59          return Ok(result);
60        }
61        Some(c) if c == self.escape => {
62          consumed += c.len_utf8();
63          match chars.next() {
64            Some(next) => {
65              consumed += next.len_utf8();
66              match (self.handler)(next) {
67                Some(replacement) => result.push(replacement),
68                None => {
69                  return Err(Fail::Cut(ParseError::from_expected(
70                    pos + consumed - next.len_utf8(),
71                    Expected::Description("valid escape sequence"),
72                  )));
73                }
74              }
75            }
76            None => {
77              return Err(Fail::Cut(ParseError::from_expected(
78                pos + consumed,
79                Expected::Description("escape character"),
80              )));
81            }
82          }
83        }
84        Some(c) => {
85          consumed += c.len_utf8();
86          result.push(c);
87        }
88        None => {
89          return Err(Fail::Cut(ParseError::from_expected(
90            pos + consumed,
91            Expected::Char(self.close),
92          )));
93        }
94      }
95    }
96  }
97}