(mut self)
| 154 | } |
| 155 | |
| 156 | fn parse_fstring_middle(mut self) -> Result<Box<Wtf8>, LexicalError> { |
| 157 | // Fast-path: if the f-string doesn't contain any escape sequences, return the literal. |
| 158 | let Some(mut index) = memchr::memchr3(b'{', b'}', b'\\', self.source.as_bytes()) else { |
| 159 | return Ok(self.source.into()); |
| 160 | }; |
| 161 | |
| 162 | let mut value = Wtf8Buf::with_capacity(self.source.len()); |
| 163 | loop { |
| 164 | // Add the characters before the escape sequence (or curly brace) to the string. |
| 165 | let before_with_slash_or_brace = self.skip_bytes(index + 1); |
| 166 | let before = &before_with_slash_or_brace[..before_with_slash_or_brace.len() - 1]; |
| 167 | value.push_str(before); |
| 168 | |
| 169 | // Add the escaped character to the string. |
| 170 | match &self.source.as_bytes()[self.cursor - 1] { |
| 171 | // If there are any curly braces inside a `FStringMiddle` token, |
| 172 | // then they were escaped (i.e. `{{` or `}}`). This means that |
| 173 | // we need increase the location by 2 instead of 1. |
| 174 | b'{' => value.push_char('{'), |
| 175 | b'}' => value.push_char('}'), |
| 176 | // We can encounter a `\` as the last character in a `FStringMiddle` |
| 177 | // token which is valid in this context. For example, |
| 178 | // |
| 179 | // ```python |
| 180 | // f"\{foo} \{bar:\}" |
| 181 | // # ^ ^^ ^ |
| 182 | // ``` |
| 183 | // |
| 184 | // Here, the `FStringMiddle` token content will be "\" and " \" |
| 185 | // which is invalid if we look at the content in isolation: |
| 186 | // |
| 187 | // ```python |
| 188 | // "\" |
| 189 | // ``` |
| 190 | // |
| 191 | // However, the content is syntactically valid in the context of |
| 192 | // the f-string because it's a substring of the entire f-string. |
| 193 | // This is still an invalid escape sequence, but we don't want to |
| 194 | // raise a syntax error as is done by the CPython parser. It might |
| 195 | // be supported in the future, refer to point 3: https://peps.python.org/pep-0701/#rejected-ideas |
| 196 | b'\\' => { |
| 197 | if !self.flags.is_raw_string() && self.peek_byte().is_some() { |
| 198 | match self.parse_escaped_char()? { |
| 199 | None => {} |
| 200 | Some(EscapedChar::Literal(c)) => value.push(c), |
| 201 | Some(EscapedChar::Escape(c)) => { |
| 202 | value.push_char('\\'); |
| 203 | value.push_char(c); |
| 204 | } |
| 205 | } |
| 206 | } else { |
| 207 | value.push_char('\\'); |
| 208 | } |
| 209 | } |
| 210 | ch => { |
| 211 | unreachable!("Expected '{{', '}}', or '\\' but got {:?}", ch); |
| 212 | } |
| 213 | } |
no test coverage detected