4
4
/// Enum for represenging extraced format string args.
5
5
/// Can either be extracted expressions (which includes identifiers),
6
6
/// or placeholders `{}`.
7
- #[ derive( Debug ) ]
7
+ #[ derive( Debug , PartialEq , Eq ) ]
8
8
pub enum Arg {
9
9
Placeholder ,
10
+ Ident ( String ) ,
10
11
Expr ( String ) ,
11
12
}
12
13
13
14
/**
14
- Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`].
15
+ Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
16
+ and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
15
17
```rust
16
- assert_eq!(vec![Arg::Expr("expr"), Arg::Placeholder, Arg::Expr("expr")], vec!["expr", "$1", "expr"])
18
+ # use ide_db::syntax_helpers::format_string_exprs::*;
19
+ assert_eq!(with_placeholders(vec![Arg::Ident("ident".to_owned()), Arg::Placeholder, Arg::Expr("expr + 2".to_owned())]), vec!["ident".to_owned(), "$1".to_owned(), "expr + 2".to_owned()])
17
20
```
18
21
*/
19
22
20
23
pub fn with_placeholders ( args : Vec < Arg > ) -> Vec < String > {
21
24
let mut placeholder_id = 1 ;
22
25
args. into_iter ( )
23
26
. map ( move |a| match a {
24
- Arg :: Expr ( s) => s,
27
+ Arg :: Expr ( s) | Arg :: Ident ( s ) => s,
25
28
Arg :: Placeholder => {
26
29
let s = format ! ( "${placeholder_id}" ) ;
27
30
placeholder_id += 1 ;
@@ -40,21 +43,22 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
40
43
Splits a format string that may contain expressions
41
44
like
42
45
```rust
43
- assert_eq!(parse("{expr } {} {expr} ").unwrap(), ("{} {} {}", vec![Arg::Expr("expr "), Arg::Placeholder, Arg::Expr("expr")]));
46
+ assert_eq!(parse("{ident } {} {expr + 42 } ").unwrap(), ("{} {} {}", vec![Arg::Ident("ident "), Arg::Placeholder, Arg::Expr("expr + 42 ")]));
44
47
```
45
48
*/
46
49
pub fn parse_format_exprs ( input : & str ) -> Result < ( String , Vec < Arg > ) , ( ) > {
47
50
#[ derive( Debug , Clone , Copy , PartialEq ) ]
48
51
enum State {
49
- NotExpr ,
50
- MaybeExpr ,
52
+ NotArg ,
53
+ MaybeArg ,
51
54
Expr ,
55
+ Ident ,
52
56
MaybeIncorrect ,
53
57
FormatOpts ,
54
58
}
55
59
60
+ let mut state = State :: NotArg ;
56
61
let mut current_expr = String :: new ( ) ;
57
- let mut state = State :: NotExpr ;
58
62
let mut extracted_expressions = Vec :: new ( ) ;
59
63
let mut output = String :: new ( ) ;
60
64
@@ -66,15 +70,15 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
66
70
let mut chars = input. chars ( ) . peekable ( ) ;
67
71
while let Some ( chr) = chars. next ( ) {
68
72
match ( state, chr) {
69
- ( State :: NotExpr , '{' ) => {
73
+ ( State :: NotArg , '{' ) => {
70
74
output. push ( chr) ;
71
- state = State :: MaybeExpr ;
75
+ state = State :: MaybeArg ;
72
76
}
73
- ( State :: NotExpr , '}' ) => {
77
+ ( State :: NotArg , '}' ) => {
74
78
output. push ( chr) ;
75
79
state = State :: MaybeIncorrect ;
76
80
}
77
- ( State :: NotExpr , _) => {
81
+ ( State :: NotArg , _) => {
78
82
if matches ! ( chr, '\\' | '$' ) {
79
83
output. push ( '\\' ) ;
80
84
}
@@ -83,71 +87,97 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
83
87
( State :: MaybeIncorrect , '}' ) => {
84
88
// It's okay, we met "}}".
85
89
output. push ( chr) ;
86
- state = State :: NotExpr ;
90
+ state = State :: NotArg ;
87
91
}
88
92
( State :: MaybeIncorrect , _) => {
89
93
// Error in the string.
90
94
return Err ( ( ) ) ;
91
95
}
92
- ( State :: MaybeExpr , '{' ) => {
96
+ // Escaped braces `{{`
97
+ ( State :: MaybeArg , '{' ) => {
93
98
output. push ( chr) ;
94
- state = State :: NotExpr ;
99
+ state = State :: NotArg ;
95
100
}
96
- ( State :: MaybeExpr , '}' ) => {
97
- // This is an empty sequence '{}'. Replace it with placeholder.
101
+ ( State :: MaybeArg , '}' ) => {
102
+ // This is an empty sequence '{}'.
98
103
output. push ( chr) ;
99
104
extracted_expressions. push ( Arg :: Placeholder ) ;
100
- state = State :: NotExpr ;
105
+ state = State :: NotArg ;
101
106
}
102
- ( State :: MaybeExpr , _) => {
107
+ ( State :: MaybeArg , _) => {
103
108
if matches ! ( chr, '\\' | '$' ) {
104
109
current_expr. push ( '\\' ) ;
105
110
}
106
111
current_expr. push ( chr) ;
107
- state = State :: Expr ;
112
+
113
+ // While Rust uses the unicode sets of XID_start and XID_continue for Identifiers
114
+ // this is probably the best we can do to avoid a false positive
115
+ if chr. is_alphabetic ( ) || chr == '_' {
116
+ state = State :: Ident ;
117
+ } else {
118
+ state = State :: Expr ;
119
+ }
108
120
}
109
- ( State :: Expr , '}' ) => {
121
+ ( State :: Ident | State :: Expr , '}' ) => {
110
122
if inexpr_open_count == 0 {
111
123
output. push ( chr) ;
112
- extracted_expressions. push ( Arg :: Expr ( current_expr. trim ( ) . into ( ) ) ) ;
124
+
125
+ if matches ! ( state, State :: Expr ) {
126
+ extracted_expressions. push ( Arg :: Expr ( current_expr. trim ( ) . into ( ) ) ) ;
127
+ } else {
128
+ extracted_expressions. push ( Arg :: Ident ( current_expr. trim ( ) . into ( ) ) ) ;
129
+ }
130
+
113
131
current_expr = String :: new ( ) ;
114
- state = State :: NotExpr ;
132
+ state = State :: NotArg ;
115
133
} else {
116
134
// We're closing one brace met before inside of the expression.
117
135
current_expr. push ( chr) ;
118
136
inexpr_open_count -= 1 ;
119
137
}
120
138
}
121
- ( State :: Expr , ':' ) if matches ! ( chars. peek( ) , Some ( ':' ) ) => {
139
+ ( State :: Ident | State :: Expr , ':' ) if matches ! ( chars. peek( ) , Some ( ':' ) ) => {
122
140
// path separator
141
+ state = State :: Expr ;
123
142
current_expr. push_str ( "::" ) ;
124
143
chars. next ( ) ;
125
144
}
126
- ( State :: Expr , ':' ) => {
145
+ ( State :: Ident | State :: Expr , ':' ) => {
127
146
if inexpr_open_count == 0 {
128
147
// We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
129
148
output. push ( chr) ;
130
- extracted_expressions. push ( Arg :: Expr ( current_expr. trim ( ) . into ( ) ) ) ;
149
+
150
+ if matches ! ( state, State :: Expr ) {
151
+ extracted_expressions. push ( Arg :: Expr ( current_expr. trim ( ) . into ( ) ) ) ;
152
+ } else {
153
+ extracted_expressions. push ( Arg :: Ident ( current_expr. trim ( ) . into ( ) ) ) ;
154
+ }
155
+
131
156
current_expr = String :: new ( ) ;
132
157
state = State :: FormatOpts ;
133
158
} else {
134
159
// We're inside of braced expression, assume that it's a struct field name/value delimiter.
135
160
current_expr. push ( chr) ;
136
161
}
137
162
}
138
- ( State :: Expr , '{' ) => {
163
+ ( State :: Ident | State :: Expr , '{' ) => {
164
+ state = State :: Expr ;
139
165
current_expr. push ( chr) ;
140
166
inexpr_open_count += 1 ;
141
167
}
142
- ( State :: Expr , _) => {
168
+ ( State :: Ident | State :: Expr , _) => {
169
+ if !( chr. is_alphanumeric ( ) || chr == '_' || chr == '#' ) {
170
+ state = State :: Expr ;
171
+ }
172
+
143
173
if matches ! ( chr, '\\' | '$' ) {
144
174
current_expr. push ( '\\' ) ;
145
175
}
146
176
current_expr. push ( chr) ;
147
177
}
148
178
( State :: FormatOpts , '}' ) => {
149
179
output. push ( chr) ;
150
- state = State :: NotExpr ;
180
+ state = State :: NotArg ;
151
181
}
152
182
( State :: FormatOpts , _) => {
153
183
if matches ! ( chr, '\\' | '$' ) {
@@ -158,7 +188,7 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
158
188
}
159
189
}
160
190
161
- if state != State :: NotExpr {
191
+ if state != State :: NotArg {
162
192
return Err ( ( ) ) ;
163
193
}
164
194
@@ -218,4 +248,20 @@ mod tests {
218
248
check ( input, output)
219
249
}
220
250
}
251
+
252
+ #[ test]
253
+ fn arg_type ( ) {
254
+ assert_eq ! (
255
+ parse_format_exprs( "{_ident} {r#raw_ident} {expr.obj} {name {thing: 42} } {}" )
256
+ . unwrap( )
257
+ . 1 ,
258
+ vec![
259
+ Arg :: Ident ( "_ident" . to_owned( ) ) ,
260
+ Arg :: Ident ( "r#raw_ident" . to_owned( ) ) ,
261
+ Arg :: Expr ( "expr.obj" . to_owned( ) ) ,
262
+ Arg :: Expr ( "name {thing: 42}" . to_owned( ) ) ,
263
+ Arg :: Placeholder
264
+ ]
265
+ ) ;
266
+ }
221
267
}
0 commit comments