diff --git a/extra_tests/snippets/fstrings.py b/extra_tests/snippets/fstrings.py index 66303f11467..c874f196dcc 100644 --- a/extra_tests/snippets/fstrings.py +++ b/extra_tests/snippets/fstrings.py @@ -47,6 +47,15 @@ spec = "0>+#10x" assert f"{16:{spec}}{foo}" == '00000+0x10bar' +part_spec = ">+#10x" +assert f"{16:0{part_spec}}{foo}" == '00000+0x10bar' + +# TODO: RUSTPYTHON, delete the next block once `test_fstring.py` can successfully parse +assert f'{10:#{1}0x}' == ' 0xa' +assert f'{10:{"#"}1{0}{"x"}}' == ' 0xa' +assert f'{-10:-{"#"}1{0}x}' == ' -0xa' +assert f'{-10:{"-"}#{1}0{"x"}}' == ' -0xa' + # TODO: # spec = "bla" # assert_raises(ValueError, lambda: f"{16:{spec}}") diff --git a/parser/src/fstring.rs b/parser/src/fstring.rs index cec5a72e3aa..e78cf39ccf0 100644 --- a/parser/src/fstring.rs +++ b/parser/src/fstring.rs @@ -11,13 +11,15 @@ use self::FStringErrorType::*; struct FStringParser<'a> { chars: iter::Peekable>, str_location: Location, + recurse_lvl: u8, } impl<'a> FStringParser<'a> { - fn new(source: &'a str, str_location: Location) -> Self { + fn new(source: &'a str, str_location: Location, recurse_lvl: u8) -> Self { Self { chars: source.chars().peekable(), str_location, + recurse_lvl, } } @@ -91,52 +93,69 @@ impl<'a> FStringParser<'a> { } ':' if delims.is_empty() => { - let mut nested = false; let mut in_nested = false; - let mut spec_expression = String::new(); + let mut spec_constructor = Vec::new(); + let mut constant_piece = String::new(); + let mut formatted_value_piece = String::new(); + let mut spec_delims = Vec::new(); while let Some(&next) = self.chars.peek() { match next { - '{' => { - if in_nested { - return Err(ExpressionNestedTooDeeply); - } - in_nested = true; - nested = true; - self.chars.next(); - continue; + '{' if in_nested => { + spec_delims.push(next); + formatted_value_piece.push(next); } - '}' => { - if in_nested { + '}' if in_nested => { + if spec_delims.is_empty() { in_nested = false; - self.chars.next(); + spec_constructor.push( + self.expr(ExprKind::FormattedValue { + value: Box::new( + FStringParser::new( + &format!("{{{}}}", formatted_value_piece), + Location::default(), + &self.recurse_lvl + 1, + ) + .parse()?, + ), + conversion: None, + format_spec: None, + }), + ); + formatted_value_piece.clear(); + } else { + spec_delims.pop(); + formatted_value_piece.push(next); } - break; } - _ => (), + _ if in_nested => { + formatted_value_piece.push(next); + } + '{' => { + in_nested = true; + spec_constructor.push(self.expr(ExprKind::Constant { + value: constant_piece.to_owned().into(), + kind: None, + })); + constant_piece.clear(); + } + '}' => break, + _ => { + constant_piece.push(next); + } } - spec_expression.push(next); self.chars.next(); } + spec_constructor.push(self.expr(ExprKind::Constant { + value: constant_piece.to_owned().into(), + kind: None, + })); + constant_piece.clear(); if in_nested { return Err(UnclosedLbrace); } - spec = Some(if nested { - Box::new( - self.expr(ExprKind::FormattedValue { - value: Box::new( - parse_fstring_expr(&spec_expression) - .map_err(|e| InvalidExpression(Box::new(e.error)))?, - ), - conversion: None, - format_spec: None, - }), - ) - } else { - Box::new(self.expr(ExprKind::Constant { - value: spec_expression.to_owned().into(), - kind: None, - })) - }) + spec = Some(Box::new(self.expr(ExprKind::JoinedStr { + values: spec_constructor, + }))) } '(' | '{' | '[' => { expression.push(ch); @@ -216,6 +235,10 @@ impl<'a> FStringParser<'a> { } fn parse(mut self) -> Result { + if self.recurse_lvl >= 2 { + return Err(ExpressionNestedTooDeeply); + } + let mut content = String::new(); let mut values = vec![]; @@ -269,7 +292,7 @@ fn parse_fstring_expr(source: &str) -> Result { /// Parse an fstring from a string, located at a certain position in the sourcecode. /// In case of errors, we will get the location and the error returned. pub fn parse_located_fstring(source: &str, location: Location) -> Result { - FStringParser::new(source, location) + FStringParser::new(source, location, 0) .parse() .map_err(|error| FStringError { error, location }) } @@ -279,7 +302,7 @@ mod tests { use super::*; fn parse_fstring(source: &str) -> Result { - FStringParser::new(source, Location::default()).parse() + FStringParser::new(source, Location::default(), 0).parse() } #[test] @@ -347,7 +370,7 @@ mod tests { assert_eq!(parse_fstring("{5!}"), Err(InvalidConversionFlag)); assert_eq!(parse_fstring("{5!x}"), Err(InvalidConversionFlag)); - assert_eq!(parse_fstring("{a:{a:{b}}"), Err(ExpressionNestedTooDeeply)); + assert_eq!(parse_fstring("{a:{a:{b}}}"), Err(ExpressionNestedTooDeeply)); assert_eq!(parse_fstring("{a:b}}"), Err(UnopenedRbrace)); assert_eq!(parse_fstring("}"), Err(UnopenedRbrace)); diff --git a/parser/src/snapshots/rustpython_parser__fstring__tests__fstring_parse_selfdocumenting_format.snap b/parser/src/snapshots/rustpython_parser__fstring__tests__fstring_parse_selfdocumenting_format.snap index e0713f5edd4..9ba81d0fb65 100644 --- a/parser/src/snapshots/rustpython_parser__fstring__tests__fstring_parse_selfdocumenting_format.snap +++ b/parser/src/snapshots/rustpython_parser__fstring__tests__fstring_parse_selfdocumenting_format.snap @@ -1,6 +1,7 @@ --- source: parser/src/fstring.rs expression: parse_ast + --- Located { location: Location { @@ -62,11 +63,22 @@ Located { column: 0, }, custom: (), - node: Constant { - value: Str( - ">10", - ), - kind: None, + node: JoinedStr { + values: [ + Located { + location: Location { + row: 0, + column: 0, + }, + custom: (), + node: Constant { + value: Str( + ">10", + ), + kind: None, + }, + }, + ], }, }, ), diff --git a/parser/src/snapshots/rustpython_parser__fstring__tests__parse_fstring_nested_spec.snap b/parser/src/snapshots/rustpython_parser__fstring__tests__parse_fstring_nested_spec.snap index bba192e305c..6bcd55732f9 100644 --- a/parser/src/snapshots/rustpython_parser__fstring__tests__parse_fstring_nested_spec.snap +++ b/parser/src/snapshots/rustpython_parser__fstring__tests__parse_fstring_nested_spec.snap @@ -37,20 +37,79 @@ Located { column: 0, }, custom: (), - node: FormattedValue { - value: Located { - location: Location { - row: 1, - column: 2, + node: JoinedStr { + values: [ + Located { + location: Location { + row: 0, + column: 0, + }, + custom: (), + node: Constant { + value: Str( + "", + ), + kind: None, + }, }, - custom: (), - node: Name { - id: "spec", - ctx: Load, + Located { + location: Location { + row: 0, + column: 0, + }, + custom: (), + node: FormattedValue { + value: Located { + location: Location { + row: 0, + column: 0, + }, + custom: (), + node: JoinedStr { + values: [ + Located { + location: Location { + row: 0, + column: 0, + }, + custom: (), + node: FormattedValue { + value: Located { + location: Location { + row: 1, + column: 2, + }, + custom: (), + node: Name { + id: "spec", + ctx: Load, + }, + }, + conversion: None, + format_spec: None, + }, + }, + ], + }, + }, + conversion: None, + format_spec: None, + }, }, - }, - conversion: None, - format_spec: None, + Located { + location: Location { + row: 0, + column: 0, + }, + custom: (), + node: Constant { + value: Str( + "", + ), + kind: None, + }, + }, + ], }, }, ), diff --git a/parser/src/snapshots/rustpython_parser__fstring__tests__parse_fstring_not_nested_spec.snap b/parser/src/snapshots/rustpython_parser__fstring__tests__parse_fstring_not_nested_spec.snap index ed40d483e88..682465de8c1 100644 --- a/parser/src/snapshots/rustpython_parser__fstring__tests__parse_fstring_not_nested_spec.snap +++ b/parser/src/snapshots/rustpython_parser__fstring__tests__parse_fstring_not_nested_spec.snap @@ -37,11 +37,22 @@ Located { column: 0, }, custom: (), - node: Constant { - value: Str( - "spec", - ), - kind: None, + node: JoinedStr { + values: [ + Located { + location: Location { + row: 0, + column: 0, + }, + custom: (), + node: Constant { + value: Str( + "spec", + ), + kind: None, + }, + }, + ], }, }, ),