Skip to content

Commit ddc154a

Browse files
f-strings: support conversion flags
1 parent d6e317b commit ddc154a

File tree

6 files changed

+80
-15
lines changed

6 files changed

+80
-15
lines changed

parser/src/ast.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,25 @@ pub enum Number {
357357
Complex { real: f64, imag: f64 },
358358
}
359359

360+
/// Transforms a value prior to formatting it.
361+
#[derive(Copy, Clone, Debug, PartialEq)]
362+
pub enum ConversionFlag {
363+
/// Converts by calling `str(<value>)`.
364+
Str,
365+
/// Converts by calling `ascii(<value>)`.
366+
Ascii,
367+
/// Converts by calling `repr(<value>)`.
368+
Repr,
369+
}
370+
360371
#[derive(Debug, PartialEq)]
361372
pub enum StringGroup {
362373
Constant {
363374
value: String,
364375
},
365376
FormattedValue {
366377
value: Box<Expression>,
378+
conversion: Option<ConversionFlag>,
367379
spec: String,
368380
},
369381
Joined {

parser/src/fstring.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ use std::str;
44

55
use lalrpop_util::ParseError as LalrpopError;
66

7-
use crate::ast::StringGroup;
7+
use crate::ast::{ConversionFlag, StringGroup};
88
use crate::lexer::{LexicalError, Location, Tok};
99
use crate::parser::parse_expression;
1010

11+
use self::FStringError::*;
1112
use self::StringGroup::*;
1213

1314
// TODO: consolidate these with ParseError
@@ -16,6 +17,7 @@ pub enum FStringError {
1617
UnclosedLbrace,
1718
UnopenedRbrace,
1819
InvalidExpression,
20+
InvalidConversionFlag,
1921
}
2022

2123
impl From<FStringError> for LalrpopError<Location, Tok, LexicalError> {
@@ -41,9 +43,23 @@ impl<'a> FStringParser<'a> {
4143
let mut expression = String::new();
4244
let mut spec = String::new();
4345
let mut depth = 0;
46+
let mut conversion = None;
4447

4548
while let Some(ch) = self.chars.next() {
4649
match ch {
50+
'!' if depth == 0 => {
51+
conversion = Some(match self.chars.next() {
52+
Some('s') => ConversionFlag::Str,
53+
Some('a') => ConversionFlag::Ascii,
54+
Some('r') => ConversionFlag::Repr,
55+
Some(_) => {
56+
return Err(InvalidConversionFlag);
57+
}
58+
None => {
59+
break;
60+
}
61+
})
62+
}
4763
':' if depth == 0 => {
4864
while let Some(&next) = self.chars.peek() {
4965
if next != '}' {
@@ -74,8 +90,9 @@ impl<'a> FStringParser<'a> {
7490
return Ok(FormattedValue {
7591
value: Box::new(
7692
parse_expression(expression.trim())
77-
.map_err(|_| FStringError::InvalidExpression)?,
93+
.map_err(|_| InvalidExpression)?,
7894
),
95+
conversion,
7996
spec,
8097
});
8198
}
@@ -86,7 +103,7 @@ impl<'a> FStringParser<'a> {
86103
}
87104
}
88105

89-
return Err(FStringError::UnclosedLbrace);
106+
return Err(UnclosedLbrace);
90107
}
91108

92109
fn parse(mut self) -> Result<StringGroup, FStringError> {
@@ -114,7 +131,7 @@ impl<'a> FStringParser<'a> {
114131
self.chars.next();
115132
content.push('}');
116133
} else {
117-
return Err(FStringError::UnopenedRbrace);
134+
return Err(UnopenedRbrace);
118135
}
119136
}
120137
_ => {
@@ -164,10 +181,12 @@ mod tests {
164181
values: vec![
165182
FormattedValue {
166183
value: Box::new(mk_ident("a")),
184+
conversion: None,
167185
spec: String::new(),
168186
},
169187
FormattedValue {
170188
value: Box::new(mk_ident("b")),
189+
conversion: None,
171190
spec: String::new(),
172191
},
173192
Constant {
@@ -190,11 +209,8 @@ mod tests {
190209

191210
#[test]
192211
fn test_parse_invalid_fstring() {
193-
assert_eq!(parse_fstring("{"), Err(FStringError::UnclosedLbrace));
194-
assert_eq!(parse_fstring("}"), Err(FStringError::UnopenedRbrace));
195-
assert_eq!(
196-
parse_fstring("{class}"),
197-
Err(FStringError::InvalidExpression)
198-
);
212+
assert_eq!(parse_fstring("{"), Err(UnclosedLbrace));
213+
assert_eq!(parse_fstring("}"), Err(UnopenedRbrace));
214+
assert_eq!(parse_fstring("{class}"), Err(InvalidExpression));
199215
}
200216
}

tests/snippets/fstrings.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,22 @@
1515
#assert f"{1 != 2}" == 'True'
1616
assert fr'x={4*10}\n' == 'x=40\\n'
1717
assert f'{16:0>+#10x}' == '00000+0x10'
18+
19+
20+
# conversion flags
21+
22+
class Value:
23+
def __format__(self, spec):
24+
return "foo"
25+
26+
def __repr__(self):
27+
return "bar"
28+
29+
def __str__(self):
30+
return "baz"
31+
32+
v = Value()
33+
34+
assert f'{v}' == 'foo'
35+
assert f'{v!r}' == 'bar'
36+
assert f'{v!s}' == 'baz'

vm/src/bytecode.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ pub enum Instruction {
169169
},
170170
Unpack,
171171
FormatValue {
172+
conversion: Option<ast::ConversionFlag>,
172173
spec: String,
173174
},
174175
}
@@ -361,7 +362,10 @@ impl Instruction {
361362
UnpackSequence { size } => w!(UnpackSequence, size),
362363
UnpackEx { before, after } => w!(UnpackEx, before, after),
363364
Unpack => w!(Unpack),
364-
FormatValue { spec } => w!(FormatValue, spec),
365+
FormatValue {
366+
conversion: _,
367+
spec,
368+
} => w!(FormatValue, spec), // TODO: write conversion
365369
}
366370
}
367371
}

vm/src/compile.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,9 +1352,16 @@ impl Compiler {
13521352
},
13531353
});
13541354
}
1355-
ast::StringGroup::FormattedValue { value, spec } => {
1355+
ast::StringGroup::FormattedValue {
1356+
value,
1357+
conversion,
1358+
spec,
1359+
} => {
13561360
self.compile_expression(value)?;
1357-
self.emit(Instruction::FormatValue { spec: spec.clone() });
1361+
self.emit(Instruction::FormatValue {
1362+
conversion: *conversion,
1363+
spec: spec.clone(),
1364+
});
13581365
}
13591366
}
13601367
Ok(())

vm/src/frame.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,8 +654,15 @@ impl Frame {
654654
}
655655
Ok(None)
656656
}
657-
bytecode::Instruction::FormatValue { spec } => {
658-
let value = self.pop_value();
657+
bytecode::Instruction::FormatValue { conversion, spec } => {
658+
use ast::ConversionFlag::*;
659+
let value = match conversion {
660+
Some(Str) => vm.to_str(&self.pop_value())?,
661+
Some(Repr) => vm.to_repr(&self.pop_value())?,
662+
Some(Ascii) => self.pop_value(), // TODO
663+
None => self.pop_value(),
664+
};
665+
659666
let spec = vm.new_str(spec.clone());
660667
let formatted = vm.call_method(&value, "__format__", vec![spec])?;
661668
self.push_value(formatted);

0 commit comments

Comments
 (0)