ifmt_impl/
lib.rs

1extern crate proc_macro;
2extern crate regex;
3#[macro_use]
4extern crate lazy_static;
5#[macro_use]
6extern crate quote;
7use proc_macro2::Span;
8use proc_macro_hack::proc_macro_hack;
9use regex::Regex;
10use syn::{Token, Error};
11
12fn consume_expr(s: &str) -> (&str, String) {
13    lazy_static! {
14        // bad hack regex to look for string/character literals
15        // we don't want to count braces contained within them
16        static ref LITERAL: Regex = Regex::new(
17            concat!(r#"^('(?:\\[\s\S]+?|\\'|[^\\'])'"#,
18                    r##"|"(?:\\[\s\S]|[^\\])+?")"##)).unwrap();
19        // have to handle raw strings separately due to no backrefs
20        static ref RAW_STRING_START: Regex = Regex::new(
21            r##"^r(#*)""##).unwrap();
22    }
23
24    let mut s = s;
25    let mut expr = String::new();
26    let mut brace_count = 1;
27    while let Some(c) = s.chars().next() {
28        match c {
29            '{' => {
30                brace_count += 1;
31                s = &s[1..];
32                expr.push('{');
33            }
34            '}' => {
35                brace_count -= 1;
36                if brace_count == 0 {
37                    return (s, expr);
38                } else {
39                    expr.push('}');
40                    s = &s[1..];
41                }
42            }
43            _ => {
44                let lit_match = LITERAL.find(s);
45                match lit_match {
46                    Some(m) => {
47                        s = &s[m.end()..];
48                        expr.push_str(m.as_str());
49                        // TODO this feels off
50                        // should we just nest the second match?
51                        continue;
52                    }
53                    None => (),
54                }
55                let raw_caps = RAW_STRING_START.captures(s);
56                match raw_caps {
57                    // lazy hack to deal with the lack of backreferences
58                    // generate the tail end of the regex
59                    Some(c) => {
60                        let m = c.get(0).unwrap();
61                        s = &s[m.end()..];
62                        expr.push_str(m.as_str());
63
64                        let cap = c.get(1).unwrap();
65                        let hash_count = cap.end() - cap.start();
66                        let end_r =
67                            Regex::new(&format!(r##"^[\s\S]+?"#{{{}}}"##, hash_count)).unwrap();
68                        let em = end_r.find(s).expect("unclosed internal raw string");
69
70                        expr.push_str(em.as_str());
71                        s = &s[em.end()..];
72                    }
73                    None => {
74                        expr.push(c);
75                        s = &s[c.len_utf8()..];
76                    }
77                }
78            }
79        }
80    }
81
82    panic!("unbalanced {");
83}
84
85macro_rules! def_ifmt_macro {
86    ($name:ident, $to_wrap:ident) => {
87        #[proc_macro_hack]
88        pub fn $name(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
89            use syn::parse_macro_input;
90            let FormatContents{fmt, args} = parse_macro_input!(tokens as FormatContents);
91            (quote! {
92                ::std::$to_wrap!(#fmt, #(#args),*)
93            }).into()
94        }
95    }
96}
97
98def_ifmt_macro!(iformat, format);
99
100def_ifmt_macro!(iprint, print);
101
102def_ifmt_macro!(iprintln, println);
103
104def_ifmt_macro!(ieprint, eprint);
105
106def_ifmt_macro!(ieprintln, eprintln);
107
108def_ifmt_macro!(iformat_args, format_args);
109
110def_ifmt_macro!(ipanic, panic);
111
112struct FormatSpec {
113    spec: String,
114}
115
116fn parse_format_type(id: &str, span: Span, input: syn::parse::ParseStream) -> syn::parse::Result<String> {
117    let mut out = String::new();
118    if id == "x" || id == "X" {
119        out.push_str(&id);
120        if input.peek(Token![?]) {
121            input.parse::<Token![?]>()?;
122            out.push('?');
123        }
124    } else if id == "o" || id == "p" || id == "b" || id == "e" || id == "E" {
125        out.push_str(&id);
126    } else if id == "s"  {
127        out.push('e');
128    } else if id == "S" {
129        out.push('E');
130    } else {
131        return Err(Error::new(span, format!("{} is not a valid format type", id)));
132    }
133    Ok(out)
134}
135
136fn parse_precision_type(input: syn::parse::ParseStream) -> syn::parse::Result<String> {
137    // ['.' precision][type]
138    // TODO make it so these can be identifiers/exprs as well? maybe in parens?
139    let mut spec = String::new();
140
141    let span = input.cursor().span();
142
143    // ['.']
144    if input.peek(Token![.]) {
145        input.parse::<Token![.]>()?;
146        spec.push('.');
147        // precision
148        let lit = input.parse::<syn::LitInt>()?;
149        let lit_str = lit.to_string();
150        if lit.suffix() != "" {
151            spec.push_str(&lit_str[..lit_str.len() - lit.suffix().len()]);
152            // [type]
153            spec.push_str(&parse_format_type(&lit.suffix(), span, input)?);
154        } else {
155            spec.push_str(&lit_str);
156        }
157    }
158
159    // [type]
160    if input.peek(Token![?]) {
161        input.parse::<Token![?]>()?;
162        spec.push('?');
163    // [type]
164    } else if input.peek(syn::Ident) {
165        let id = input.parse::<syn::Ident>()?.to_string();
166        spec.push_str(&parse_format_type(&id, span, input)?);
167    }
168
169    Ok(spec)
170}
171
172
173impl syn::parse::Parse for FormatSpec {
174    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
175        /*
176        format_spec := [[fill]align][sign]['#']['0'][width]['.' precision][type]
177        fill := character
178        align := '<' | '^' | '>'
179        sign := '+' | '-'
180        width := count
181        precision := count | '*'
182        type := identifier | '?' | ''
183        count := parameter | integer
184        parameter := argument '$'
185        */
186        input.parse::<Token![;]>()?;
187        let mut spec = String::new();
188        // [[fill]align]
189        if input.peek(syn::LitChar) {
190            let pad = input.parse::<syn::LitChar>().unwrap();
191            spec.push(pad.value());
192            let lookahead = input.lookahead1();
193            if lookahead.peek(Token![<]) {
194                input.parse::<Token![<]>()?;
195                spec.push('<');
196            } else if lookahead.peek(Token![>]) {
197                input.parse::<Token![>]>()?;
198                spec.push('>');
199            } else if lookahead.peek(Token![^]) {
200                input.parse::<Token![^]>()?;
201                spec.push('^');
202            } else {
203                return Err(lookahead.error());
204            }
205        } else if input.peek(Token![<]) {
206            input.parse::<Token![<]>()?;
207            spec.push('<');
208        } else if input.peek(Token![>]) {
209            input.parse::<Token![>]>()?;
210            spec.push('>');
211        } else if input.peek(Token![^]) {
212            input.parse::<Token![^]>()?;
213            spec.push('^');
214        }
215
216        // [sign]
217        if input.peek(Token![+]) {
218            input.parse::<Token![+]>()?;
219            spec.push('+');
220        } else if input.peek(Token![-]) {
221            input.parse::<Token![-]>()?;
222            spec.push('-');
223        }
224
225        // ['#']
226        if input.peek(Token![#]) {
227            input.parse::<Token![#]>()?;
228            spec.push('#');
229        }
230
231        // TODO width as an expression
232        //  will require some reworking - has to be passed by name
233        //  FormatSpec either has to generate an incomplete spec which is templated later
234        //  or it needs information on which number expression it's on
235        // ['0'][width]['.' precision][type]
236        if input.peek(syn::LitFloat) {
237            // ['0'][width]['.' precision][type]
238            let span = input.cursor().span();
239            let lit = input.parse::<syn::LitFloat>()?;
240            let lit_str = lit.to_string();
241            if lit.suffix() != "" {
242                // ['0'][width]['.' precision]
243                spec.push_str(&lit_str[..lit_str.len()-lit.suffix().len()]);
244                // [type]
245                spec.push_str(&parse_format_type(&lit.suffix(), span, input)?);
246            } else {
247                // ['0'][width]['.' precision]
248                spec.push_str(&lit_str);
249                // [type]
250                if input.peek(syn::Ident) {
251                    let id = input.parse::<syn::Ident>()?.to_string();
252                    spec.push_str(&parse_format_type(&id, span, input)?);
253                }
254            }
255        } else if input.peek(syn::LitInt) {
256            // ['0'][width][type]
257            let span = input.cursor().span();
258            let lit = input.parse::<syn::LitInt>()?;
259            let lit_str = lit.to_string();
260            if lit.suffix() != "" {
261                spec.push_str(&lit_str[..lit_str.len()-lit.suffix().len()]);
262                // [type]
263                spec.push_str(&parse_format_type(&lit.suffix(), span, input)?);
264            } else {
265                // ['.' precision][type]
266                spec.push_str(&lit_str);
267                spec.push_str(&parse_precision_type(input)?);
268            }
269        } else {
270            spec.push_str(&parse_precision_type(input)?);
271        }
272
273        Ok(FormatSpec { spec: spec })
274    }
275}
276
277struct FormatContents {
278    fmt: String,
279    args: Vec<syn::Expr>,
280}
281
282impl FormatContents {
283    fn parse_inlit(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
284        lazy_static! {
285            static ref SPEC: Regex =
286            Regex::new(r":([\s\S]?[<^>])?([\+\-])?(#)?(0)?(\d+|\*)?(\.\d+)?(\?|\w+)?$").unwrap();
287        }
288        let format_str = input.parse::<syn::LitStr>()?.value();
289
290        let mut format_lit = String::from("");
291        let mut exprs = vec![];
292        let mut s: &str = &format_str;
293        while let Some(c) = s.chars().next() {
294            match c {
295                '{' => {
296                    s = &s[1..];
297                    match s.chars().next() {
298                        Some('{') => format_lit.push_str("{{"),
299                        _ => {
300                            let (new_s, mut expr) = consume_expr(s);
301                            s = new_s;
302                            let spec_match = SPEC.find(&expr);
303                            match spec_match {
304                                Some(m) => {
305                                    format_lit.push('{');
306                                    format_lit.push_str(m.as_str());
307                                    format_lit.push('}');
308                                    let si = m.start();
309
310                                    expr.truncate(si);
311                                }
312                                None => {
313                                    format_lit.push_str("{}");
314                                }
315                            }
316
317                            exprs.push(syn::parse_str::<syn::Expr>(&expr)?);
318                        }
319                    }
320                }
321                '}' => {
322                    s = &s[1..];
323                    if s.chars().next() == Some('}') {
324                        format_lit.push_str("}}");
325                    } else {
326                        panic!("unmatched }");
327                    }
328                }
329                _ => format_lit.push(c),
330            }
331            s = &s[c.len_utf8()..];
332        }
333
334        Ok(FormatContents{fmt: format_lit, args: exprs})
335    }
336
337    fn parse_outlit(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
338        let mut expect_expr = true; // state machine
339        let mut fmt = String::new();
340        let mut args = vec![];
341
342        while !input.is_empty() {
343            let lookahead = input.lookahead1();
344            if lookahead.peek(syn::LitStr) {
345                let ls = input.parse::<syn::LitStr>().unwrap();
346                fmt += &ls.value().replace("{", "{{").replace("}", "}}");
347                expect_expr = true;
348            } else if expect_expr {
349                // Bug in proc_macro_hack(fake_call_site): iformat!("x:" 2 + );'s
350                // "unexpected end of input" error span points at the first token in the macro
351                // invocation rather than the end.
352                // not going to try to work around - probably not a common mistake and
353                // if proc_macro_hack becomes a pass-through as mentioned by dtolnay
354                // (or, barring that, if I remove it as a dependency) the issue goes away
355                let expr = input.parse::<syn::Expr>()?;
356                args.push(expr);
357                fmt.push_str("{");
358                if input.peek(Token![;]) {
359                    let spec = &input.parse::<FormatSpec>()?.spec;
360                    if !spec.is_empty() {
361                        fmt.push_str(":");
362                        fmt.push_str(spec);
363                    }
364                }
365                fmt.push('}');
366                expect_expr = false;
367            } else {
368                return Err(lookahead.error());
369            }
370        }
371        Ok(FormatContents{fmt: fmt, args: args})
372    }
373}
374
375impl syn::parse::Parse for FormatContents {
376    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
377        // note: eventually one of these branches should go away
378        let forked = input.fork();
379        if forked.peek(syn::LitStr) && {forked.parse::<syn::LitStr>()?; forked.is_empty()} {
380            FormatContents::parse_inlit(input)
381        } else {
382            FormatContents::parse_outlit(input)
383        }
384    }
385}
386
387struct WriteFormat {
388    buf: syn::Expr,
389    fmt_contents: FormatContents
390}
391
392impl syn::parse::Parse for WriteFormat {
393    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
394        Ok(WriteFormat {
395            buf: input.parse::<syn::Expr>()?,
396            fmt_contents:  if !input.is_empty() {
397                    input.parse::<Token![,]>()?;
398                    input.parse::<FormatContents>()?
399                } else {
400                    FormatContents{fmt: "".to_string(), args: vec![]}
401                }
402        })
403    }
404}
405
406#[proc_macro_hack]
407pub fn iwrite(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
408    use syn::parse_macro_input;
409    let WriteFormat{buf, fmt_contents: FormatContents{fmt, args}} = parse_macro_input!(tokens as WriteFormat);
410    (quote! {
411        ::std::write!(#buf, #fmt, #(#args),*)
412    }).into()
413}
414
415#[proc_macro_hack]
416pub fn iwriteln(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
417    use syn::parse_macro_input;
418    let WriteFormat{buf, fmt_contents: FormatContents{fmt, args}} = parse_macro_input!(tokens as WriteFormat);
419    (quote! {
420        ::std::writeln!(#buf, #fmt, #(#args),*)
421    }).into()
422}