@@ -26,9 +26,33 @@ impl<'a> FStringParser<'a> {
26
26
let mut spec = None ;
27
27
let mut delims = Vec :: new ( ) ;
28
28
let mut conversion = None ;
29
+ let mut pred_expression_text = String :: new ( ) ;
30
+ let mut trailing_seq = String :: new ( ) ;
29
31
30
32
while let Some ( ch) = self . chars . next ( ) {
31
33
match ch {
34
+ // can be integrated better with the remainign code, but as a starting point ok
35
+ // in general I would do here a tokenizing of the fstrings to omit this peeking.
36
+ '!' if self . chars . peek ( ) == Some ( & '=' ) => {
37
+ expression. push_str ( "!=" ) ;
38
+ self . chars . next ( ) ;
39
+ }
40
+
41
+ '=' if self . chars . peek ( ) == Some ( & '=' ) => {
42
+ expression. push_str ( "==" ) ;
43
+ self . chars . next ( ) ;
44
+ }
45
+
46
+ '>' if self . chars . peek ( ) == Some ( & '=' ) => {
47
+ expression. push_str ( ">=" ) ;
48
+ self . chars . next ( ) ;
49
+ }
50
+
51
+ '<' if self . chars . peek ( ) == Some ( & '=' ) => {
52
+ expression. push_str ( "<=" ) ;
53
+ self . chars . next ( ) ;
54
+ }
55
+
32
56
'!' if delims. is_empty ( ) && self . chars . peek ( ) != Some ( & '=' ) => {
33
57
if expression. trim ( ) . is_empty ( ) {
34
58
return Err ( EmptyExpression ) ;
@@ -46,10 +70,22 @@ impl<'a> FStringParser<'a> {
46
70
}
47
71
} ) ;
48
72
49
- if self . chars . peek ( ) != Some ( & '}' ) {
73
+ if let Some ( & peek) = self . chars . peek ( ) {
74
+ if peek != '}' && peek != ':' {
75
+ return Err ( ExpectedRbrace ) ;
76
+ }
77
+ } else {
50
78
return Err ( ExpectedRbrace ) ;
51
79
}
52
80
}
81
+
82
+ // match a python 3.8 self documenting expression
83
+ // format '{' PYTHON_EXPRESSION '=' FORMAT_SPECIFIER? '}'
84
+ '=' if self . chars . peek ( ) != Some ( & '=' ) => {
85
+ // check for delims empty?
86
+ pred_expression_text = expression. to_string ( ) ; // safe expression before = to print it
87
+ }
88
+
53
89
':' if delims. is_empty ( ) => {
54
90
let mut nested = false ;
55
91
let mut in_nested = false ;
@@ -121,14 +157,35 @@ impl<'a> FStringParser<'a> {
121
157
if expression. is_empty ( ) {
122
158
return Err ( EmptyExpression ) ;
123
159
}
124
- return Ok ( FormattedValue {
125
- value : Box :: new (
126
- parse_expression ( expression. trim ( ) )
127
- . map_err ( |e| InvalidExpression ( Box :: new ( e. error ) ) ) ?,
128
- ) ,
129
- conversion,
130
- spec,
131
- } ) ;
160
+ if pred_expression_text. is_empty ( ) {
161
+ return Ok ( FormattedValue {
162
+ value : Box :: new (
163
+ parse_expression ( expression. trim ( ) )
164
+ . map_err ( |e| InvalidExpression ( Box :: new ( e. error ) ) ) ?,
165
+ ) ,
166
+ conversion,
167
+ spec,
168
+ } ) ;
169
+ } else {
170
+ return Ok ( Joined {
171
+ values : vec ! [
172
+ Constant {
173
+ value: pred_expression_text + "=" ,
174
+ } ,
175
+ Constant {
176
+ value: trailing_seq,
177
+ } ,
178
+ FormattedValue {
179
+ value: Box :: new(
180
+ parse_expression( expression. trim( ) )
181
+ . map_err( |e| InvalidExpression ( Box :: new( e. error) ) ) ?,
182
+ ) ,
183
+ conversion,
184
+ spec,
185
+ } ,
186
+ ] ,
187
+ } ) ;
188
+ }
132
189
}
133
190
'"' | '\'' => {
134
191
expression. push ( ch) ;
@@ -139,12 +196,14 @@ impl<'a> FStringParser<'a> {
139
196
}
140
197
}
141
198
}
199
+ ' ' if !pred_expression_text. is_empty ( ) => {
200
+ trailing_seq. push ( ch) ;
201
+ }
142
202
_ => {
143
203
expression. push ( ch) ;
144
204
}
145
205
}
146
206
}
147
-
148
207
Err ( UnclosedLbrace )
149
208
}
150
209
@@ -298,12 +357,35 @@ mod tests {
298
357
) ;
299
358
}
300
359
360
+ #[ test]
361
+ fn test_fstring_parse_selfdocumenting_base ( ) {
362
+ let src = String :: from ( "{user=}" ) ;
363
+ let parse_ast = parse_fstring ( & src) ;
364
+
365
+ assert ! ( parse_ast. is_ok( ) ) ;
366
+ }
367
+
368
+ #[ test]
369
+ fn test_fstring_parse_selfdocumenting_base_more ( ) {
370
+ let src = String :: from ( "mix {user=} with text and {second=}" ) ;
371
+ let parse_ast = parse_fstring ( & src) ;
372
+
373
+ assert ! ( parse_ast. is_ok( ) ) ;
374
+ }
375
+
376
+ #[ test]
377
+ fn test_fstring_parse_selfdocumenting_format ( ) {
378
+ let src = String :: from ( "{user=:>10}" ) ;
379
+ let parse_ast = parse_fstring ( & src) ;
380
+
381
+ assert ! ( parse_ast. is_ok( ) ) ;
382
+ }
383
+
301
384
#[ test]
302
385
fn test_parse_invalid_fstring ( ) {
303
386
assert_eq ! ( parse_fstring( "{5!a" ) , Err ( ExpectedRbrace ) ) ;
304
387
assert_eq ! ( parse_fstring( "{5!a1}" ) , Err ( ExpectedRbrace ) ) ;
305
388
assert_eq ! ( parse_fstring( "{5!" ) , Err ( ExpectedRbrace ) ) ;
306
-
307
389
assert_eq ! ( parse_fstring( "abc{!a 'cat'}" ) , Err ( EmptyExpression ) ) ;
308
390
assert_eq ! ( parse_fstring( "{!a" ) , Err ( EmptyExpression ) ) ;
309
391
assert_eq ! ( parse_fstring( "{ !a}" ) , Err ( EmptyExpression ) ) ;
@@ -318,6 +400,8 @@ mod tests {
318
400
assert_eq ! ( parse_fstring( "{a:{b}" ) , Err ( UnclosedLbrace ) ) ;
319
401
assert_eq ! ( parse_fstring( "{" ) , Err ( UnclosedLbrace ) ) ;
320
402
403
+ assert_eq ! ( parse_fstring( "{}" ) , Err ( EmptyExpression ) ) ;
404
+
321
405
// TODO: check for InvalidExpression enum?
322
406
assert ! ( parse_fstring( "{class}" ) . is_err( ) ) ;
323
407
}
@@ -328,4 +412,25 @@ mod tests {
328
412
let parse_ast = parse_fstring ( & source) ;
329
413
assert ! ( parse_ast. is_ok( ) ) ;
330
414
}
415
+
416
+ #[ test]
417
+ fn test_parse_fstring_equals ( ) {
418
+ let source = String :: from ( "{42 == 42}" ) ;
419
+ let parse_ast = parse_fstring ( & source) ;
420
+ assert ! ( parse_ast. is_ok( ) ) ;
421
+ }
422
+
423
+ #[ test]
424
+ fn test_parse_fstring_selfdoc_prec_space ( ) {
425
+ let source = String :: from ( "{x =}" ) ;
426
+ let parse_ast = parse_fstring ( & source) ;
427
+ assert ! ( parse_ast. is_ok( ) ) ;
428
+ }
429
+
430
+ #[ test]
431
+ fn test_parse_fstring_selfdoc_trailing_space ( ) {
432
+ let source = String :: from ( "{x= }" ) ;
433
+ let parse_ast = parse_fstring ( & source) ;
434
+ assert ! ( parse_ast. is_ok( ) ) ;
435
+ }
331
436
}
0 commit comments