Skip to main content

oni_comb_parser/text/
quoted_string.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 QuotedString;
10
11pub fn quoted_string() -> QuotedString {
12  QuotedString
13}
14
15impl<'a> Parser<StrInput<'a>> for QuotedString {
16  type Error = ParseError;
17  type Output = String;
18
19  #[inline]
20  fn parse_next(&mut self, input: &mut StrInput<'a>) -> PResult<String, ParseError> {
21    let pos = input.offset();
22    let remaining = input.as_str();
23    let mut chars = remaining.chars();
24
25    // opening quote
26    match chars.next() {
27      Some('"') => {}
28      _ => {
29        return Err(Fail::Backtrack(ParseError::expected_char(pos, '"')));
30      }
31    }
32
33    let mut result = String::new();
34    let mut consumed = 1; // opening quote
35
36    loop {
37      match chars.next() {
38        Some('"') => {
39          consumed += 1;
40          input.advance(consumed);
41          return Ok(result);
42        }
43        Some('\\') => {
44          consumed += 1;
45          match chars.next() {
46            Some('"') => {
47              consumed += 1;
48              result.push('"');
49            }
50            Some('\\') => {
51              consumed += 1;
52              result.push('\\');
53            }
54            Some('/') => {
55              consumed += 1;
56              result.push('/');
57            }
58            Some('b') => {
59              consumed += 1;
60              result.push('\u{0008}');
61            }
62            Some('f') => {
63              consumed += 1;
64              result.push('\u{000C}');
65            }
66            Some('n') => {
67              consumed += 1;
68              result.push('\n');
69            }
70            Some('r') => {
71              consumed += 1;
72              result.push('\r');
73            }
74            Some('t') => {
75              consumed += 1;
76              result.push('\t');
77            }
78            Some('u') => {
79              consumed += 1;
80              let mut hex = String::with_capacity(4);
81              for _ in 0..4 {
82                match chars.next() {
83                  Some(c) if c.is_ascii_hexdigit() => {
84                    consumed += c.len_utf8();
85                    hex.push(c);
86                  }
87                  _ => {
88                    return Err(Fail::Cut(ParseError::expected_description(
89                      pos + consumed,
90                      "4 hex digits after \\u",
91                    )));
92                  }
93                }
94              }
95              let code_point = u32::from_str_radix(&hex, 16).unwrap();
96              match char::from_u32(code_point) {
97                Some(c) => result.push(c),
98                None => {
99                  return Err(Fail::Cut(ParseError::expected_description(
100                    pos + consumed - 4,
101                    "valid unicode code point",
102                  )));
103                }
104              }
105            }
106            Some(_) => {
107              return Err(Fail::Cut(ParseError::expected_description(
108                pos + consumed,
109                "valid escape sequence",
110              )));
111            }
112            None => {
113              return Err(Fail::Cut(ParseError::expected_description(
114                pos + consumed,
115                "escape character after '\\'",
116              )));
117            }
118          }
119        }
120        Some(c) => {
121          consumed += c.len_utf8();
122          result.push(c);
123        }
124        None => {
125          return Err(Fail::Cut(ParseError::expected_char(pos + consumed, '"')));
126        }
127      }
128    }
129  }
130}