Skip to main content

oni_comb_parser/text/
quoted_string_cow.rs

1use alloc::borrow::Cow;
2use alloc::string::String;
3
4use crate::error::ParseError;
5use crate::fail::{Fail, PResult};
6use crate::input::Input;
7use crate::parser::Parser;
8use crate::str_input::StrInput;
9
10/// エスケープなし文字列はゼロコピー (`&'a str`) で返し、
11/// エスケープありの場合のみ `String` にフォールバックする `quoted_string` パーサー。
12pub struct QuotedStringCow;
13
14pub fn quoted_string_cow() -> QuotedStringCow {
15  QuotedStringCow
16}
17
18impl<'a> Parser<StrInput<'a>> for QuotedStringCow {
19  type Error = ParseError;
20  type Output = Cow<'a, str>;
21
22  #[inline]
23  fn parse_next(&mut self, input: &mut StrInput<'a>) -> PResult<Cow<'a, str>, ParseError> {
24    let pos = input.offset();
25    let remaining = input.as_str();
26    let bytes = remaining.as_bytes();
27
28    if bytes.is_empty() || bytes[0] != b'"' {
29      return Err(Fail::Backtrack(ParseError::expected_char(pos, '"')));
30    }
31
32    // Fast path: scan for closing quote without escape
33    let mut i = 1; // skip opening quote
34    loop {
35      if i >= bytes.len() {
36        // No closing quote found
37        return Err(Fail::Cut(ParseError::expected_char(pos + i, '"')));
38      }
39      match bytes[i] {
40        b'"' => {
41          // No escape encountered — zero-copy slice
42          let s = &remaining[1..i];
43          input.advance(i + 1);
44          return Ok(Cow::Borrowed(s));
45        }
46        b'\\' => {
47          // Escape found — fall through to slow path
48          break;
49        }
50        _ => {
51          i += 1;
52        }
53      }
54    }
55
56    // Slow path: build String, reusing the prefix before the first escape
57    let mut result = String::with_capacity(i + 16);
58    result.push_str(&remaining[1..i]);
59
60    let mut chars = remaining[i..].chars();
61    let mut consumed = i; // bytes consumed so far (including opening quote)
62
63    loop {
64      match chars.next() {
65        Some('"') => {
66          consumed += 1;
67          input.advance(consumed);
68          return Ok(Cow::Owned(result));
69        }
70        Some('\\') => {
71          consumed += 1;
72          match chars.next() {
73            Some('"') => {
74              consumed += 1;
75              result.push('"');
76            }
77            Some('\\') => {
78              consumed += 1;
79              result.push('\\');
80            }
81            Some('/') => {
82              consumed += 1;
83              result.push('/');
84            }
85            Some('b') => {
86              consumed += 1;
87              result.push('\u{0008}');
88            }
89            Some('f') => {
90              consumed += 1;
91              result.push('\u{000C}');
92            }
93            Some('n') => {
94              consumed += 1;
95              result.push('\n');
96            }
97            Some('r') => {
98              consumed += 1;
99              result.push('\r');
100            }
101            Some('t') => {
102              consumed += 1;
103              result.push('\t');
104            }
105            Some('u') => {
106              consumed += 1;
107              let mut code: u32 = 0;
108              for _ in 0..4 {
109                match chars.next() {
110                  Some(c) if c.is_ascii_hexdigit() => {
111                    consumed += 1;
112                    code = code * 16 + c.to_digit(16).unwrap();
113                  }
114                  _ => {
115                    return Err(Fail::Cut(ParseError::expected_description(
116                      pos + consumed,
117                      "4 hex digits after \\u",
118                    )));
119                  }
120                }
121              }
122              match char::from_u32(code) {
123                Some(c) => result.push(c),
124                None => {
125                  return Err(Fail::Cut(ParseError::expected_description(
126                    pos + consumed - 4,
127                    "valid unicode code point",
128                  )));
129                }
130              }
131            }
132            Some(_) => {
133              return Err(Fail::Cut(ParseError::expected_description(
134                pos + consumed,
135                "valid escape sequence",
136              )));
137            }
138            None => {
139              return Err(Fail::Cut(ParseError::expected_description(
140                pos + consumed,
141                "escape character after '\\'",
142              )));
143            }
144          }
145        }
146        Some(c) => {
147          consumed += c.len_utf8();
148          result.push(c);
149        }
150        None => {
151          return Err(Fail::Cut(ParseError::expected_char(pos + consumed, '"')));
152        }
153      }
154    }
155  }
156}