(
mut self,
)
| 278 | } |
| 279 | |
| 280 | fn parse_interpolated_string_middle( |
| 281 | mut self, |
| 282 | ) -> Result<ast::InterpolatedStringLiteralElement, LexicalError> { |
| 283 | // Fast-path: if the f-string or t-string doesn't contain any escape sequences, return the literal. |
| 284 | let Some(mut index) = memchr::memchr3(b'{', b'}', b'\\', self.source.as_bytes()) else { |
| 285 | return Ok(ast::InterpolatedStringLiteralElement { |
| 286 | value: self.source.into(), |
| 287 | range: self.range, |
| 288 | node_index: AtomicNodeIndex::NONE, |
| 289 | }); |
| 290 | }; |
| 291 | |
| 292 | let mut value = String::with_capacity(self.source.len()); |
| 293 | loop { |
| 294 | // Add the characters before the escape sequence (or curly brace) to the string. |
| 295 | let before_with_slash_or_brace = self.skip_bytes(index + 1); |
| 296 | let before = &before_with_slash_or_brace[..before_with_slash_or_brace.len() - 1]; |
| 297 | value.push_str(before); |
| 298 | |
| 299 | // Add the escaped character to the string. |
| 300 | match self.source.as_bytes()[self.cursor - 1] { |
| 301 | // If there are any curly braces inside a `F/TStringMiddle` token, |
| 302 | // then they were escaped (i.e. `{{` or `}}`). This means that |
| 303 | // the raw source contains a doubled brace, but the literal value only |
| 304 | // contains one brace. |
| 305 | brace @ (b'{' | b'}') => { |
| 306 | if self.peek_byte() == Some(brace) { |
| 307 | self.next_byte(); |
| 308 | } |
| 309 | value.push(char::from(brace)); |
| 310 | } |
| 311 | // We can encounter a `\` as the last character in a `F/TStringMiddle` |
| 312 | // token which is valid in this context. For example, |
| 313 | // |
| 314 | // ```python |
| 315 | // f"\{foo} \{bar:\}" |
| 316 | // # ^ ^^ ^ |
| 317 | // ``` |
| 318 | // |
| 319 | // Here, the `F/TStringMiddle` token content will be "\" and " \" |
| 320 | // which is invalid if we look at the content in isolation: |
| 321 | // |
| 322 | // ```python |
| 323 | // "\" |
| 324 | // ``` |
| 325 | // |
| 326 | // However, the content is syntactically valid in the context of |
| 327 | // the f/t-string because it's a substring of the entire f/t-string. |
| 328 | // This is still an invalid escape sequence, but we don't want to |
| 329 | // raise a syntax error as is done by the CPython parser. It might |
| 330 | // be supported in the future, refer to point 3: https://peps.python.org/pep-0701/#rejected-ideas |
| 331 | b'\\' => { |
| 332 | if !self.flags.is_raw_string() && self.peek_byte().is_some() { |
| 333 | if let Some(brace @ (b'{' | b'}')) = self.peek_byte() |
| 334 | && self.source.as_bytes().get(self.cursor + 1).copied() == Some(brace) |
| 335 | { |
| 336 | // Leave the doubled brace for the next iteration to collapse. |
| 337 | value.push('\\'); |
no test coverage detected