mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
implemented selfdocumenting fstrings and tests from cpython- current handling of quotes is too simple
This commit is contained in:
1 parent
94a7b48560
commit
34478d3db7
2 files changed
+142
-7
No files matched your search
+62
-6
@@ -27,9 +27,36 @@ impl<'a> FStringParser<'a> {
|
||||
let mut delims = Vec::new();
|
||||
let mut conversion = None;
|
||||
let mut pred_expression_text = String::new();
|
||||
let mut trailing_seq=String::new();
|
||||
|
||||
while let Some(ch) = self.chars.next() {
|
||||
match ch {
|
||||
// can be integrated better with the remainign code, but as a starting point ok
|
||||
// in general I would do here a tokenizing of the fstrings to omit this peeking.
|
||||
'!' if self.chars.peek() == Some(&'=') => {
|
||||
expression.push('!');
|
||||
expression.push('=');
|
||||
self.chars.next();
|
||||
}
|
||||
|
||||
'=' if self.chars.peek() == Some(&'=') => {
|
||||
expression.push('=');
|
||||
expression.push('=');
|
||||
self.chars.next();
|
||||
}
|
||||
|
||||
'>' if self.chars.peek() == Some(&'=') => {
|
||||
expression.push('>');
|
||||
expression.push('=');
|
||||
self.chars.next();
|
||||
}
|
||||
|
||||
'<' if self.chars.peek() == Some(&'=') => {
|
||||
expression.push('<');
|
||||
expression.push('=');
|
||||
self.chars.next();
|
||||
}
|
||||
|
||||
'!' if delims.is_empty() && self.chars.peek() != Some(&'=') => {
|
||||
if expression.trim().is_empty() {
|
||||
return Err(EmptyExpression);
|
||||
@@ -54,8 +81,9 @@ impl<'a> FStringParser<'a> {
|
||||
|
||||
// match a python 3.8 self documenting expression
|
||||
// format '{' PYTHON_EXPRESSION '=' FORMAT_SPECIFIER? '}'
|
||||
'=' if self.chars.peek() != Some(&'=') => {
|
||||
pred_expression_text = expression.trim().to_string(); // use expression and remove whitespace
|
||||
'=' if self.chars.peek() != Some(&'=') => { // check for delims empty?
|
||||
pred_expression_text = expression.to_string(); // safe expression before = to print it
|
||||
// pred_expression_text contains trailing spaces, which are trimmed somewhere later before printing, this needs to be prevented orovercome here
|
||||
}
|
||||
|
||||
':' if delims.is_empty() => {
|
||||
@@ -143,7 +171,11 @@ impl<'a> FStringParser<'a> {
|
||||
return Ok(Joined{
|
||||
values:vec![
|
||||
Constant{
|
||||
value:pred_expression_text.to_owned()
|
||||
value:pred_expression_text.to_owned()+"="
|
||||
},
|
||||
|
||||
Constant {
|
||||
value:trailing_seq.to_owned()
|
||||
},
|
||||
|
||||
FormattedValue {
|
||||
@@ -167,6 +199,11 @@ impl<'a> FStringParser<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' ' if !pred_expression_text.is_empty() => {
|
||||
trailing_seq.push(ch);
|
||||
}
|
||||
|
||||
_ => {
|
||||
expression.push(ch);
|
||||
}
|
||||
@@ -370,9 +407,7 @@ mod tests {
|
||||
assert_eq!(parse_fstring("{a:{b}"), Err(UnclosedLbrace));
|
||||
assert_eq!(parse_fstring("{"), Err(UnclosedLbrace));
|
||||
|
||||
// TODO: which err?
|
||||
//assert_eq!(parse_fstring("{}"), Err()); // which one?
|
||||
assert!(parse_fstring("{}").is_err()); // take this for the moment
|
||||
assert_eq!(parse_fstring("{}"), Err(EmptyExpression));
|
||||
|
||||
// TODO: check for InvalidExpression enum?
|
||||
assert!(parse_fstring("{class}").is_err());
|
||||
@@ -384,4 +419,25 @@ mod tests {
|
||||
let parse_ast = parse_fstring(&source);
|
||||
assert!(parse_ast.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_equals() {
|
||||
let source = String::from("{42 == 42}");
|
||||
let parse_ast = parse_fstring(&source);
|
||||
assert!(parse_ast.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_selfdoc_prec_space() {
|
||||
let source = String::from("{x =}");
|
||||
let parse_ast = parse_fstring(&source);
|
||||
assert!(parse_ast.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_selfdoc_trailing_space() {
|
||||
let source = String::from("{x= }");
|
||||
let parse_ast = parse_fstring(&source);
|
||||
assert!(parse_ast.is_ok());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
from testutils import assert_raises
|
||||
#from testutils import assert_raises
|
||||
foo = 'bar'
|
||||
|
||||
assert f"{''}" == ''
|
||||
@@ -17,6 +17,13 @@ assert fr'x={4*10}\n' == 'x=40\\n'
|
||||
assert f'{16:0>+#10x}' == '00000+0x10'
|
||||
assert f"{{{(lambda x: f'hello, {x}')('world}')}" == '{hello, world}'
|
||||
|
||||
assert f'{foo=}' == 'foo=bar'
|
||||
|
||||
num=42
|
||||
assert f'{num=}' == 'num=42'
|
||||
assert f'{num=:>10}' == 'num= 42'
|
||||
|
||||
|
||||
spec = "0>+#10x"
|
||||
assert f"{16:{spec}}{foo}" == '00000+0x10bar'
|
||||
|
||||
@@ -61,3 +68,75 @@ assert f'>{v}' == '>\u262e'
|
||||
assert f'>{v!r}' == ">'\u262e'"
|
||||
assert f'>{v!s}' == '>\u262e'
|
||||
assert f'>{v!a}' == r">'\u262e'"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Tests for fstring selfdocumenting form CPython
|
||||
|
||||
class C:
|
||||
def assertEqual(self, a,b):
|
||||
assert a==b, "{0} == {1}".format(a,b)
|
||||
|
||||
self=C()
|
||||
|
||||
x = 'A string'
|
||||
self.assertEqual(f'{10=}', '10=10')
|
||||
self.assertEqual(f'{x=}', 'x=' + x )#repr(x)) # TODO: add ' when printing strings
|
||||
#self.assertEqual(f'{x =}', 'x =' + x )# + repr(x)) # TODO: remove trim in impl
|
||||
# self.assertEqual(f'{x=!s}', 'x=' + str(x)) # TODO : implement format specs properly
|
||||
# self.assertEqual(f'{x=!r}', 'x=' + x) #repr(x))
|
||||
#self.assertEqual(f'{x=!a}', 'x=' + ascii(x))
|
||||
|
||||
x = 2.71828
|
||||
self.assertEqual(f'{x=:.2f}', 'x=' + format(x, '.2f'))
|
||||
self.assertEqual(f'{x=:}', 'x=' + format(x, ''))
|
||||
# self.assertEqual(f'{x=!r:^20}', 'x=' + format(repr(x), '^20'))
|
||||
# self.assertEqual(f'{x=!s:^20}', 'x=' + format(str(x), '^20'))
|
||||
# self.assertEqual(f'{x=!a:^20}', 'x=' + format(ascii(x), '^20'))
|
||||
|
||||
x = 9
|
||||
self.assertEqual(f'{3*x+15=}', '3*x+15=42')
|
||||
|
||||
# There is code in ast.c that deals with non-ascii expression values. So,
|
||||
# use a unicode identifier to trigger that.
|
||||
tenπ = 31.4
|
||||
self.assertEqual(f'{tenπ=:.2f}', 'tenπ=31.40')
|
||||
|
||||
# Also test with Unicode in non-identifiers.
|
||||
#self.assertEqual(f'{"Σ"=}', '"Σ"=\'Σ\'') ' TODO ' missing
|
||||
|
||||
# Make sure nested fstrings still work.
|
||||
self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', '*****3.1415=3.1*****')
|
||||
|
||||
# Make sure text before and after an expression with = works
|
||||
# correctly.
|
||||
pi = 'π'
|
||||
#self.assertEqual(f'alpha α {pi=} ω omega', "alpha α pi='π' ω omega") # ' missing around pi
|
||||
|
||||
# Check multi-line expressions.
|
||||
#self.assertEqual(f'''{3=}''', '\n3\n=3') # TODO: multiline f strings not supported, seems to be an rustpython issue
|
||||
|
||||
# Since = is handled specially, make sure all existing uses of
|
||||
# it still work.
|
||||
|
||||
self.assertEqual(f'{0==1}', 'False')
|
||||
self.assertEqual(f'{0!=1}', 'True')
|
||||
self.assertEqual(f'{0<=1}', 'True')
|
||||
self.assertEqual(f'{0>=1}', 'False')
|
||||
|
||||
# Make sure leading and following text works.
|
||||
x = 'foo'
|
||||
#self.assertEqual(f'X{x=}Y', 'Xx='+repr(x)+'Y') # TODO '
|
||||
self.assertEqual(f'X{x=}Y', 'Xx='+x+'Y') # just for the moment
|
||||
|
||||
# Make sure whitespace around the = works.
|
||||
# self.assertEqual(f'X{x =}Y', 'Xx ='+repr(x)+'Y') # TODO '
|
||||
# self.assertEqual(f'X{x= }Y', 'Xx= '+repr(x)+'Y') # TODO '
|
||||
# self.assertEqual(f'X{x = }Y', 'Xx = '+repr(x)+'Y') # TODO '
|
||||
|
||||
# TODO remove trim
|
||||
self.assertEqual(f'X{x =}Y', 'Xx ='+x+'Y')
|
||||
self.assertEqual(f'X{x= }Y', 'Xx= '+x+'Y')
|
||||
self.assertEqual(f'X{x = }Y', 'Xx = '+x+'Y')
|
||||
Reference in new issue
Block a user