Skip to content

Commit 03179b5

Browse files
authored
Parse arbitrarily nested f-strings (#1026)
1 parent 552af63 commit 03179b5

File tree

3 files changed

+37
-17
lines changed

3 files changed

+37
-17
lines changed

native/libcst/src/tokenizer/core/mod.rs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -907,8 +907,8 @@ impl<'t> TokState<'t> {
907907
return Err(TokError::UnterminatedString);
908908
}
909909
(ch @ Some('\''), _) | (ch @ Some('"'), _) => {
910-
// see if this actually terminates something in fstring_stack
911-
for node in self.fstring_stack.iter() {
910+
// see if this actually terminates the most recent fstring
911+
if let Some(node) = self.fstring_stack.last() {
912912
if ch == Some(node.quote_char.into()) {
913913
match node.quote_size {
914914
StringQuoteSize::Single => {
@@ -1004,27 +1004,18 @@ impl<'t> TokState<'t> {
10041004

10051005
fn maybe_consume_fstring_end(&mut self) -> Option<TokType> {
10061006
let ch = self.text_pos.peek();
1007-
let mut match_idx = None;
1008-
for (idx, node) in self.fstring_stack.iter().enumerate() {
1007+
if let Some(node) = self.fstring_stack.last() {
10091008
if ch == Some(node.quote_char.into()) {
10101009
if node.quote_size == StringQuoteSize::Triple {
1011-
if self.text_pos.consume(node.quote_char.triple_str()) {
1012-
match_idx = Some(idx);
1013-
break;
1014-
}
1010+
self.text_pos.consume(node.quote_char.triple_str());
10151011
} else {
10161012
self.text_pos.next(); // already matched
1017-
match_idx = Some(idx);
1018-
break;
10191013
}
1014+
self.fstring_stack.pop();
1015+
return Some(TokType::FStringEnd);
10201016
}
10211017
}
1022-
if let Some(match_idx) = match_idx {
1023-
self.fstring_stack.truncate(match_idx);
1024-
Some(TokType::FStringEnd)
1025-
} else {
1026-
None
1027-
}
1018+
None
10281019
}
10291020
}
10301021

native/libcst/src/tokenizer/tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,3 +853,25 @@ fn test_nested_f_string_specs() {
853853
])
854854
)
855855
}
856+
857+
#[test]
858+
fn test_nested_f_strings() {
859+
let config = TokConfig {
860+
split_fstring: true,
861+
..default_config()
862+
};
863+
assert_eq!(
864+
tokenize_all("f'{f'{2}'}'", &config),
865+
Ok(vec![
866+
(TokType::FStringStart, "f'"),
867+
(TokType::Op, "{"),
868+
(TokType::FStringStart, "f'"),
869+
(TokType::Op, "{"),
870+
(TokType::Number, "2"),
871+
(TokType::Op, "}"),
872+
(TokType::FStringEnd, "'"),
873+
(TokType::Op, "}"),
874+
(TokType::FStringEnd, "'")
875+
])
876+
)
877+
}

native/libcst/tests/fixtures/super_strings.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,11 @@
3131
f"regexp_like(path, '.*\{file_type}$')"
3232
f"\lfoo"
3333

34-
f"{_:{_:}{a}}"
34+
f"{_:{_:}{a}}"
35+
36+
f"foo {f"bar {x}"} baz"
37+
f'some words {a+b:.3f} more words {c+d=} final words'
38+
f"{'':*^{1:{1}}}"
39+
f"{'':*^{1:{1:{1}}}}"
40+
f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
41+

0 commit comments

Comments
 (0)