Skip to content

Commit

Permalink
Parse arbitrarily nested f-strings (Instagram#1026)
Browse files Browse the repository at this point in the history
  • Loading branch information
zsol authored and manmartgarc committed Oct 3, 2023
1 parent 86ae4b4 commit 6b55923
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 17 deletions.
23 changes: 7 additions & 16 deletions native/libcst/src/tokenizer/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -907,8 +907,8 @@ impl<'t> TokState<'t> {
return Err(TokError::UnterminatedString);
}
(ch @ Some('\''), _) | (ch @ Some('"'), _) => {
// see if this actually terminates something in fstring_stack
for node in self.fstring_stack.iter() {
// see if this actually terminates the most recent fstring
if let Some(node) = self.fstring_stack.last() {
if ch == Some(node.quote_char.into()) {
match node.quote_size {
StringQuoteSize::Single => {
Expand Down Expand Up @@ -1004,27 +1004,18 @@ impl<'t> TokState<'t> {

fn maybe_consume_fstring_end(&mut self) -> Option<TokType> {
let ch = self.text_pos.peek();
let mut match_idx = None;
for (idx, node) in self.fstring_stack.iter().enumerate() {
if let Some(node) = self.fstring_stack.last() {
if ch == Some(node.quote_char.into()) {
if node.quote_size == StringQuoteSize::Triple {
if self.text_pos.consume(node.quote_char.triple_str()) {
match_idx = Some(idx);
break;
}
self.text_pos.consume(node.quote_char.triple_str());
} else {
self.text_pos.next(); // already matched
match_idx = Some(idx);
break;
}
self.fstring_stack.pop();
return Some(TokType::FStringEnd);
}
}
if let Some(match_idx) = match_idx {
self.fstring_stack.truncate(match_idx);
Some(TokType::FStringEnd)
} else {
None
}
None
}
}

Expand Down
22 changes: 22 additions & 0 deletions native/libcst/src/tokenizer/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,3 +853,25 @@ fn test_nested_f_string_specs() {
])
)
}

#[test]
fn test_nested_f_strings() {
let config = TokConfig {
split_fstring: true,
..default_config()
};
assert_eq!(
tokenize_all("f'{f'{2}'}'", &config),
Ok(vec![
(TokType::FStringStart, "f'"),
(TokType::Op, "{"),
(TokType::FStringStart, "f'"),
(TokType::Op, "{"),
(TokType::Number, "2"),
(TokType::Op, "}"),
(TokType::FStringEnd, "'"),
(TokType::Op, "}"),
(TokType::FStringEnd, "'")
])
)
}
9 changes: 8 additions & 1 deletion native/libcst/tests/fixtures/super_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@
f"regexp_like(path, '.*\{file_type}$')"
f"\lfoo"

f"{_:{_:}{a}}"
f"{_:{_:}{a}}"

f"foo {f"bar {x}"} baz"
f'some words {a+b:.3f} more words {c+d=} final words'
f"{'':*^{1:{1}}}"
f"{'':*^{1:{1:{1}}}}"
f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"

0 comments on commit 6b55923

Please sign in to comment.