Scan a JSON string starting right after the opening quote. # Arguments `s` - The string slice starting at the first character after the opening `"` `char_offset` - The character index where this slice starts (for error messages) `strict` - Whether to reject control characters # Returns `Ok((result, chars_consumed, bytes_consumed))` - The decoded string and how much was consumed `Err(DecodeError)
(
s: &'a Wtf8,
char_offset: usize,
strict: bool,
)
| 141 | /// * `Ok((result, chars_consumed, bytes_consumed))` - The decoded string and how much was consumed |
| 142 | /// * `Err(DecodeError)` - If the string is malformed |
| 143 | pub fn scanstring<'a>( |
| 144 | s: &'a Wtf8, |
| 145 | char_offset: usize, |
| 146 | strict: bool, |
| 147 | ) -> Result<(Wtf8Buf, usize, usize), DecodeError> { |
| 148 | flame_guard!("machinery::scanstring"); |
| 149 | let unterminated_err = || DecodeError::new("Unterminated string starting at", char_offset - 1); |
| 150 | |
| 151 | let bytes = s.as_bytes(); |
| 152 | |
| 153 | // Fast path: use memchr to find " or \ quickly |
| 154 | if let Some(pos) = memchr2(b'"', b'\\', bytes) |
| 155 | && bytes[pos] == b'"' |
| 156 | { |
| 157 | let content_bytes = &bytes[..pos]; |
| 158 | |
| 159 | // In strict mode, check for control characters (0x00-0x1F) |
| 160 | let has_control_char = strict && content_bytes.iter().any(|&b| b < 0x20); |
| 161 | |
| 162 | if !has_control_char { |
| 163 | flame_guard!("machinery::scanstring::fast_path"); |
| 164 | let result_slice = &s[..pos]; |
| 165 | let char_count = result_slice.code_points().count(); |
| 166 | let mut out = Wtf8Buf::with_capacity(pos); |
| 167 | out.push_wtf8(result_slice); |
| 168 | // +1 for the closing quote |
| 169 | return Ok((out, char_count + 1, pos + 1)); |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | // Slow path: chunk-based parsing for strings with escapes or control chars |
| 174 | flame_guard!("machinery::scanstring::slow_path"); |
| 175 | let mut chunks: Vec<StrOrChar<'a>> = Vec::new(); |
| 176 | let mut output_len = 0usize; |
| 177 | let mut push_chunk = |chunk: StrOrChar<'a>| { |
| 178 | output_len += chunk.len(); |
| 179 | chunks.push(chunk); |
| 180 | }; |
| 181 | |
| 182 | let mut chars = s.code_point_indices().enumerate().peekable(); |
| 183 | let mut chunk_start: usize = 0; |
| 184 | |
| 185 | while let Some((char_i, (byte_i, c))) = chars.next() { |
| 186 | match c.to_char_lossy() { |
| 187 | '"' => { |
| 188 | push_chunk(StrOrChar::Str(&s[chunk_start..byte_i])); |
| 189 | flame_guard!("machinery::scanstring::assemble_chunks"); |
| 190 | let mut out = Wtf8Buf::with_capacity(output_len); |
| 191 | for x in chunks { |
| 192 | match x { |
| 193 | StrOrChar::Str(s) => out.push_wtf8(s), |
| 194 | StrOrChar::Char(c) => out.push(c), |
| 195 | } |
| 196 | } |
| 197 | // +1 for the closing quote |
| 198 | return Ok((out, char_i + 1, byte_i + 1)); |
| 199 | } |
| 200 | '\\' => { |