Skip to main content

oni_comb_parser/text/
escaped.rs

1use alloc::string::String;
2
3use crate::error::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::expected_char(pos, self.open)));
45      }
46    }
47
48    let mut result = String::new();
49    let mut consumed = self.open.len_utf8();
50
51    loop {
52      match chars.next() {
53        Some(c) if c == self.close => {
54          consumed += c.len_utf8();
55          input.advance(consumed);
56          return Ok(result);
57        }
58        Some(c) if c == self.escape => {
59          consumed += c.len_utf8();
60          match chars.next() {
61            Some(next) => {
62              consumed += next.len_utf8();
63              match (self.handler)(next) {
64                Some(replacement) => result.push(replacement),
65                None => {
66                  return Err(Fail::Cut(ParseError::expected_description(
67                    pos + consumed - next.len_utf8(),
68                    "valid escape sequence",
69                  )));
70                }
71              }
72            }
73            None => {
74              return Err(Fail::Cut(ParseError::expected_description(
75                pos + consumed,
76                "escape character",
77              )));
78            }
79          }
80        }
81        Some(c) => {
82          consumed += c.len_utf8();
83          result.push(c);
84        }
85        None => {
86          return Err(Fail::Cut(ParseError::expected_char(pos + consumed, self.close)));
87        }
88      }
89    }
90  }
91}