8000 Merge pull request #1625 from palaviv/f-string-nesting · RustPython/RustPython@ddb61e2 · GitHub
[go: up one dir, main page]

Skip to content

Commit ddb61e2

Browse files
authored
Merge pull request #1625 from palaviv/f-string-nesting
Support expression in f-strings spec
2 parents f22a12d + 6a4c3da commit ddb61e2

File tree

8 files changed

+110
-15
lines changed

8 files changed

+110
-15
lines changed

bytecode/src/bytecode.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ pub enum Instruction {
274274
},
275275
FormatValue {
276276
conversion: Option<ConversionFlag>,
277-
spec: String,
278277
},
279278
PopException,
280279
Reverse {
@@ -564,7 +563,7 @@ impl Instruction {
564563
LoadBuildClass => w!(LoadBuildClass),
565564
UnpackSequence { size } => w!(UnpackSequence, size),
566565
UnpackEx { before, after } => w!(UnpackEx, before, after),
567-
FormatValue { spec, .. } => w!(FormatValue, spec), // TODO: write conversion
566+
FormatValue { .. } => w!(FormatValue), // TODO: write conversion
568567
PopException => w!(PopException),
569568
Reverse { amount } => w!(Reverse, amount),
570569
GetAwaitable => w!(GetAwaitable),

compiler/src/compile.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2108,10 +2108,17 @@ impl<O: OutputStream> Compiler<O> {
21082108
conversion,
21092109
spec,
21102110
} => {
2111+
match spec {
2112+
Some(spec) => self.compile_string(spec)?,
2113+
None => self.emit(Instruction::LoadConst {
2114+
value: bytecode::Constant::String {
2115+
value: String::new(),
2116+
},
2117+
}),
2118+
};
21112119
self.compile_expression(value)?;
21122120
self.emit(Instruction::FormatValue {
21132121
conversion: conversion.map(compile_conversion_flag),
2114-
spec: spec.clone(),
21152122
});
21162123
}
21172124
}

compiler/src/symboltable.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,8 +726,11 @@ impl SymbolTableBuilder {
726726
fn scan_string_group(&mut self, group: &ast::StringGroup) -> SymbolTableResult {
727727
match group {
728728
ast::StringGroup::Constant { .. } => {}
729-
ast::StringGroup::FormattedValue { value, .. } => {
729+
ast::StringGroup::FormattedValue { value, spec, .. } => {
730730
self.scan_expression(value, &ExpressionContext::Load)?;
731+
if let Some(spec) = spec {
732+
self.scan_string_group(spec)?;
733+
}
731734
}
732735
ast::StringGroup::Joined { values } => {
733736
for subgroup in values {

parser/src/ast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ pub enum StringGroup {
505505
FormattedValue {
506506
value: Box<Expression>,
507507
conversion: Option<ConversionFlag>,
508-
spec: String,
508+
spec: Option<Box<StringGroup>>,
509509
},
510510
Joined {
511511
values: Vec<StringGroup>,

parser/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ pub enum FStringErrorType {
8181
InvalidConversionFlag,
8282
EmptyExpression,
8383
MismatchedDelimiter,
84+
ExpressionNestedTooDeeply,
8485
}
8586

8687
impl fmt::Display for FStringErrorType {
@@ -94,6 +95,9 @@ impl fmt::Display for FStringErrorType {
9495
FStringErrorType::InvalidConversionFlag => write!(f, "Invalid conversion flag"),
9596
FStringErrorType::EmptyExpression => write!(f, "Empty expression"),
9697
FStringErrorType::MismatchedDelimiter => write!(f, "Mismatched delimiter"),
98+
FStringErrorType::ExpressionNestedTooDeeply => {
99+
write!(f, "expressions nested too deeply")
100+
}
97101
}
98102
}
99103
}

parser/src/fstring.rs

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl<'a> FStringParser<'a> {
2323

2424
fn parse_formatted_value(&mut self) -> Result<StringGroup, FStringErrorType> {
2525
let mut expression = String::new();
26-
let mut spec = String::new();
26+
let mut spec = None;
2727
let mut delims = Vec::new();
2828
let mut conversion = None;
2929

@@ -43,13 +43,48 @@ impl<'a> FStringParser<'a> {
4343
})
4444
}
4545
':' if delims.is_empty() => {
46+
let mut nested = false;
47+
let mut in_nested = false;
48+
let mut spec_expression = String::new();
4649
while let Some(&next) = self.chars.peek() {
47-
if next != '}' {
48-
spec.push(next);
49-
self.chars.next();
50-
} else {
51-
break;
50+
match next {
51+
'{' => {
52+
if in_nested {
53+
return Err(ExpressionNestedTooDeeply);
54+
}
55+
in_nested = true;
56+
nested = true;
57+
self.chars.next();
58+
continue;
59+
}
60+
'}' => {
61+
if in_nested {
62+
in_nested = false;
63+
self.chars.next();
64+
}
65+
break;
66+
}
67+
_ => (),
5268
}
69+
spec_expression.push(next);
70+
self.chars.next();
71+
}
72+
if in_nested {
73+
return Err(UnclosedLbrace);
74+
}
75+
if nested {
76+
spec = Some(Box::new(FormattedValue {
77+
value: Box::new(
78+
parse_expression(spec_expression.trim())
79+
.map_err(|e| InvalidExpression(Box::new(e.error)))?,
80+
),
81+
conversion: None,
82+
spec: None,
83+
}))
84+
} else {
85+
spec = Some(Box::new(Constant {
86+
value: spec_expression.trim().to_string(),
87+
}))
5388
}
5489
}
5590
'(' | '{' | '[' => {
@@ -194,12 +229,12 @@ mod tests {
194229
FormattedValue {
195230
value: Box::new(mk_ident("a", 1, 1)),
196231
conversion: None,
197-
spec: String::new(),
232+
spec: None,
198233
},
199234
FormattedValue {
200235
value: Box::new(mk_ident("b", 1, 1)),
201236
conversion: None,
202-
spec: String::new(),
237+
spec: None,
203238
},
204239
Constant {
205240
value: "{foo}".to_owned()
@@ -209,6 +244,42 @@ mod tests {
209244
);
210245
}
211246

247+
#[test]
248+
fn test_parse_fstring_nested_spec() {
249+
let source = String::from("{foo:{spec}}");
250+
let parse_ast = parse_fstring(&source).unwrap();
251+
252+
assert_eq!(
253+
parse_ast,
254+
FormattedValue {
255+
value: Box::new(mk_ident("foo", 1, 1)),
256+
conversion: None,
257+
spec: Some(Box::new(FormattedValue {
258+
value: Box::new(mk_ident("spec", 1, 1)),
259+
conversion: None,
260+
spec: None,
261+
})),
262+
}
263+
);
264+
}
265+
266+
#[test]
267+
fn test_parse_fstring_not_nested_spec() {
268+
let source = String::from("{foo:spec}");
269+
let parse_ast = parse_fstring(&source).unwrap();
270+
271+
assert_eq!(
272+
parse_ast,
273+
FormattedValue {
274+
value: Box::new(mk_ident("foo", 1, 1)),
275+
conversion: None,
276+
spec: Some(Box::new(Constant {
277+
value: "spec".to_string(),
278+
})),
279+
}
280+
);
281+
}
282+
212283
#[test]
213284
fn test_parse_empty_fstring() {
214285
assert_eq!(
@@ -223,6 +294,9 @@ mod tests {
223294
fn test_parse_invalid_fstring() {
224295
assert_eq!(parse_fstring("{"), Err(UnclosedLbrace));
225296
assert_eq!(parse_fstring("}"), Err(UnopenedRbrace));
297+
assert_eq!(parse_fstring("{a:{a:{b}}"), Err(ExpressionNestedTooDeeply));
298+
assert_eq!(parse_fstring("{a:b}}"), Err(UnopenedRbrace));
299+
assert_eq!(parse_fstring("{a:{b}"), Err(UnclosedLbrace));
226300

227301
// TODO: check for InvalidExpression enum?
228302
assert!(parse_fstring("{class}").is_err());

tests/snippets/fstrings.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from testutils import assert_raises
12
foo = 'bar'
23

34
assert f"{''}" == ''
@@ -16,6 +17,13 @@
1617
assert f'{16:0>+#10x}' == '00000+0x10'
1718
assert f"{{{(lambda x: f'hello, {x}')('world}')}" == '{hello, world}'
1819

20+
spec = "0>+#10x"
21+
assert f"{16:{spec}}{foo}" == '00000+0x10bar'
22+
23+
# TODO:
24+
# spec = "bla"
25+
# assert_raises(ValueError, lambda: f"{16:{spec}}")
26+
1927
# Normally `!` cannot appear outside of delimiters in the expression but
2028
# cpython makes an exception for `!=`, so we should too.
2129

vm/src/frame.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ impl Frame {
616616
bytecode::Instruction::UnpackEx { before, after } => {
617617
self.execute_unpack_ex(vm, *before, *after)
618618
}
619-
bytecode::Instruction::FormatValue { conversion, spec } => {
619+
bytecode::Instruction::FormatValue { conversion } => {
620620
use bytecode::ConversionFlag::*;
621621
let value = match conversion {
622622
Some(Str) => vm.to_str(&self.pop_value())?.into_object(),
@@ -625,7 +625,7 @@ impl Frame {
625625
None => self.pop_value(),
626626
};
627627

628-
let spec = vm.new_str(spec.clone());
628+
let spec = vm.to_str(&self.pop_value())?.into_object();
629629
let formatted = vm.call_method(&value, "__format__", vec![spec])?;
630630
self.push_value(formatted);
631631
Ok(None)

0 commit comments

Comments
 (0)
0