44/// Enum for represenging extraced format string args.
55/// Can either be extracted expressions (which includes identifiers),
66/// or placeholders `{}`.
7- #[ derive( Debug ) ]
7+ #[ derive( Debug , PartialEq , Eq ) ]
88pub enum Arg {
99 Placeholder ,
10+ Ident ( String ) ,
1011 Expr ( String ) ,
1112}
1213
1314/**
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.
1517 ```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()])
1720 ```
1821*/
1922
2023pub fn with_placeholders ( args : Vec < Arg > ) -> Vec < String > {
2124 let mut placeholder_id = 1 ;
2225 args. into_iter ( )
2326 . map ( move |a| match a {
24- Arg :: Expr ( s) => s,
27+ Arg :: Expr ( s) | Arg :: Ident ( s ) => s,
2528 Arg :: Placeholder => {
2629 let s = format ! ( "${placeholder_id}" ) ;
2730 placeholder_id += 1 ;
@@ -40,21 +43,22 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
4043 Splits a format string that may contain expressions
4144 like
4245 ```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 ")]));
4447 ```
4548*/
4649pub fn parse_format_exprs ( input : & str ) -> Result < ( String , Vec < Arg > ) , ( ) > {
4750 #[ derive( Debug , Clone , Copy , PartialEq ) ]
4851 enum State {
49- NotExpr ,
50- MaybeExpr ,
52+ NotArg ,
53+ MaybeArg ,
5154 Expr ,
55+ Ident ,
5256 MaybeIncorrect ,
5357 FormatOpts ,
5458 }
5559
60+ let mut state = State :: NotArg ;
5661 let mut current_expr = String :: new ( ) ;
57- let mut state = State :: NotExpr ;
5862 let mut extracted_expressions = Vec :: new ( ) ;
5963 let mut output = String :: new ( ) ;
6064
@@ -66,15 +70,15 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
6670 let mut chars = input. chars ( ) . peekable ( ) ;
6771 while let Some ( chr) = chars. next ( ) {
6872 match ( state, chr) {
69- ( State :: NotExpr , '{' ) => {
73+ ( State :: NotArg , '{' ) => {
7074 output. push ( chr) ;
71- state = State :: MaybeExpr ;
75+ state = State :: MaybeArg ;
7276 }
73- ( State :: NotExpr , '}' ) => {
77+ ( State :: NotArg , '}' ) => {
7478 output. push ( chr) ;
7579 state = State :: MaybeIncorrect ;
7680 }
77- ( State :: NotExpr , _) => {
81+ ( State :: NotArg , _) => {
7882 if matches ! ( chr, '\\' | '$' ) {
7983 output. push ( '\\' ) ;
8084 }
@@ -83,71 +87,97 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
8387 ( State :: MaybeIncorrect , '}' ) => {
8488 // It's okay, we met "}}".
8589 output. push ( chr) ;
86- state = State :: NotExpr ;
90+ state = State :: NotArg ;
8791 }
8892 ( State :: MaybeIncorrect , _) => {
8993 // Error in the string.
9094 return Err ( ( ) ) ;
9195 }
92- ( State :: MaybeExpr , '{' ) => {
96+ // Escaped braces `{{`
97+ ( State :: MaybeArg , '{' ) => {
9398 output. push ( chr) ;
94- state = State :: NotExpr ;
99+ state = State :: NotArg ;
95100 }
96- ( State :: MaybeExpr , '}' ) => {
97- // This is an empty sequence '{}'. Replace it with placeholder.
101+ ( State :: MaybeArg , '}' ) => {
102+ // This is an empty sequence '{}'.
98103 output. push ( chr) ;
99104 extracted_expressions. push ( Arg :: Placeholder ) ;
100- state = State :: NotExpr ;
105+ state = State :: NotArg ;
101106 }
102- ( State :: MaybeExpr , _) => {
107+ ( State :: MaybeArg , _) => {
103108 if matches ! ( chr, '\\' | '$' ) {
104109 current_expr. push ( '\\' ) ;
105110 }
106111 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+ }
108120 }
109- ( State :: Expr , '}' ) => {
121+ ( State :: Ident | State :: Expr , '}' ) => {
110122 if inexpr_open_count == 0 {
111123 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+
113131 current_expr = String :: new ( ) ;
114- state = State :: NotExpr ;
132+ state = State :: NotArg ;
115133 } else {
116134 // We're closing one brace met before inside of the expression.
117135 current_expr. push ( chr) ;
118136 inexpr_open_count -= 1 ;
119137 }
120138 }
121- ( State :: Expr , ':' ) if matches ! ( chars. peek( ) , Some ( ':' ) ) => {
139+ ( State :: Ident | State :: Expr , ':' ) if matches ! ( chars. peek( ) , Some ( ':' ) ) => {
122140 // path separator
141+ state = State :: Expr ;
123142 current_expr. push_str ( "::" ) ;
124143 chars. next ( ) ;
125144 }
126- ( State :: Expr , ':' ) => {
145+ ( State :: Ident | State :: Expr , ':' ) => {
127146 if inexpr_open_count == 0 {
128147 // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
129148 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+
131156 current_expr = String :: new ( ) ;
132157 state = State :: FormatOpts ;
133158 } else {
134159 // We're inside of braced expression, assume that it's a struct field name/value delimiter.
135160 current_expr. push ( chr) ;
136161 }
137162 }
138- ( State :: Expr , '{' ) => {
163+ ( State :: Ident | State :: Expr , '{' ) => {
164+ state = State :: Expr ;
139165 current_expr. push ( chr) ;
140166 inexpr_open_count += 1 ;
141167 }
142- ( State :: Expr , _) => {
168+ ( State :: Ident | State :: Expr , _) => {
169+ if !( chr. is_alphanumeric ( ) || chr == '_' || chr == '#' ) {
170+ state = State :: Expr ;
171+ }
172+
143173 if matches ! ( chr, '\\' | '$' ) {
144174 current_expr. push ( '\\' ) ;
145175 }
146176 current_expr. push ( chr) ;
147177 }
148178 ( State :: FormatOpts , '}' ) => {
149179 output. push ( chr) ;
150- state = State :: NotExpr ;
180+ state = State :: NotArg ;
151181 }
152182 ( State :: FormatOpts , _) => {
153183 if matches ! ( chr, '\\' | '$' ) {
@@ -158,7 +188,7 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
158188 }
159189 }
160190
161- if state != State :: NotExpr {
191+ if state != State :: NotArg {
162192 return Err ( ( ) ) ;
163193 }
164194
@@ -218,4 +248,20 @@ mod tests {
218248 check ( input, output)
219249 }
220250 }
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+ }
221267}
0 commit comments