codegen_template/
lib.rs

1use proc_macro::TokenStream;
2use unscanny::Scanner;
3
4/// Interpolation kind
5enum Kind<'a> {
6    Display(&'a str),
7    Iterator(&'a str),
8    Call(&'a str),
9}
10
11/// Match allowed character at the beginning of a Rust identifier
12fn id_start(c: char) -> bool {
13    unicode_xid::UnicodeXID::is_xid_start(c) || c == '_'
14}
15
16/// Match allowed character after the beginning of a Rust identifier
17fn id_continue(c: char) -> bool {
18    unicode_xid::UnicodeXID::is_xid_continue(c)
19}
20
21/// Try to parse a Rust identifier from `scan`
22fn parse_ident<'a>(scan: &mut Scanner<'a>) -> Option<&'a str> {
23    if scan.at(id_start) {
24        Some(scan.eat_while(id_continue))
25    } else if scan.eat_if('{') {
26        scan.eat_whitespace();
27        let start = scan.cursor();
28        scan.eat_while(id_continue);
29        let ident = scan.from(start);
30        scan.eat_whitespace();
31        scan.expect('}');
32        scan.eat_whitespace();
33        Some(ident)
34    } else {
35        None
36    }
37}
38
39/// Parse the next raw string and optional pattern pair from `scan`
40/// Return ("", None) when reach EOF
41fn parse_next<'a>(scan: &mut Scanner<'a>) -> (&'a str, Option<Kind<'a>>) {
42    let raw = scan.eat_until('$');
43
44    if scan.eat_if('$') {
45        scan.eat_whitespace();
46        let pattern = if let Some(ident) = parse_ident(scan) {
47            Some(Kind::Display(ident))
48        } else if scan.eat_if('!') {
49            scan.eat_whitespace();
50            if let Some(ident) = parse_ident(scan) {
51                Some(Kind::Call(ident))
52            } else {
53                panic!("Unknown pattern $!{}", scan.eat_while(|_| true))
54            }
55        } else if scan.eat_if('(') {
56            let start = scan.cursor();
57            let mut count_delim = 0;
58            while let Some(c) = scan.peek() {
59                match c {
60                    ')' if count_delim == 0 => break,
61                    ')' => count_delim -= 1,
62                    '(' => count_delim += 1,
63                    _ => {}
64                }
65                scan.eat();
66            }
67            let inner = scan.from(start);
68            scan.expect(')');
69            scan.eat_whitespace();
70            Some(Kind::Iterator(inner))
71        } else {
72            panic!("Unknown pattern ${}", scan.eat_while(|_| true))
73        };
74
75        // Remove whitespace until next pattern
76        let start = scan.cursor();
77        scan.eat_whitespace();
78        if !scan.at("$") {
79            scan.jump(start);
80        }
81
82        (raw, pattern)
83    } else {
84        (raw, None)
85    }
86}
87
88/// Find all non iterator interpolated identifier in `scan`
89fn ident_in_iterator<'a>(scan: &'a mut Scanner) -> Vec<&'a str> {
90    let start = scan.cursor();
91    let mut idents = Vec::new();
92    while let (_, Some(pat)) = parse_next(scan) {
93        match pat {
94            Kind::Display(ident) | Kind::Call(ident) => idents.push(ident),
95            Kind::Iterator(_) => panic!("nested repetitions are not supported"),
96        }
97    }
98    scan.jump(start);
99    idents
100}
101
102/// Generate code to write raw `str` into `out`
103fn gen_str(s: &mut String, out: &str, str: &str) {
104    if !str.is_empty() {
105        s.push_str(out);
106        s.push_str(".write_str(\"");
107        for c in str.chars() {
108            if c == '"' {
109                s.push_str("\\\"")
110            } else {
111                s.push(c)
112            }
113        }
114        s.push_str("\").unwrap();\n");
115    }
116}
117
118/// Generate code to write displayable `ident` into `out`
119fn gen_disp(s: &mut String, out: &str, ident: &str) {
120    s.push_str(out);
121    s.push_str(".write_fmt(format_args!(\"{}\",");
122    s.push_str(ident);
123    s.push_str(")).unwrap();\n");
124}
125
126/// Generate code to write interpolation patterns in `scan` into `out`
127fn gen_recursive<'a>(scan: &'a mut Scanner, s: &mut String, out: &str) {
128    loop {
129        let (raw, pattern) = parse_next(scan);
130        if raw.is_empty() && pattern.is_none() {
131            break;
132        }
133        gen_str(s, out, raw);
134        if let Some(pattern) = pattern {
135            match pattern {
136                Kind::Display(ident) => gen_disp(s, out, ident),
137                Kind::Call(ident) => {
138                    s.push_str("let w = &mut*w;");
139                    s.push_str(ident);
140                    s.push_str("(&mut *");
141                    s.push_str(out);
142                    s.push_str(");\n");
143                }
144                Kind::Iterator(inner) => {
145                    let mut scan = Scanner::new(inner);
146                    let idents = ident_in_iterator(&mut scan);
147                    let mut iter = idents.iter();
148                    s.push_str("{\nlet iter = ");
149                    s.push_str(iter.next().unwrap());
150                    s.push_str(".clone()");
151                    for item in iter {
152                        s.push_str(".zip(");
153                        s.push_str(item);
154                        s.push_str(".clone())");
155                    }
156                    s.push_str(";\n");
157                    s.push_str("for ");
158                    for _ in 1..idents.len() {
159                        s.push('(');
160                    }
161                    let mut iter = idents.iter();
162                    s.push_str(iter.next().unwrap());
163                    for item in iter {
164                        s.push(',');
165                        s.push_str(item);
166                        s.push(')');
167                    }
168                    s.push_str(" in iter {\n");
169                    gen_recursive(&mut scan, s, out);
170                    s.push_str("}\n}\n");
171                }
172            }
173        }
174    }
175}
176
177/// Performs variable interpolation against the input and store the result into
178/// a writable output.
179///
180/// # Display
181///
182/// You can interpolate any type implementing the [`Display`](std::fmt::Display) trait using `$var`
183/// or `${var}`. This grabs the `var` variable that is currently in scope and
184/// format it into the output.
185///
186/// # Lazy
187///
188/// You can interpolate formatting closure implementing the [`Fn(&mut W)`] trait
189/// using `$!lazy` or `$!{lazy}`. This grabs the `lazy` variable that is currently
190/// in scope and call it with th output as arg in the right time.
191///
192/// # Repetition
193///
194/// Repetition is done using `$(...)`. This iterates through the elements of any variable
195/// interpolated within the repetition and inserts a copy of the repetition body
196/// for each one. The variables in an interpolation must implement the [`Iterator`] and the
197/// [`Clone`] traits.
198///
199/// - `$($var)` — simple repetition
200/// - `$( struct ${var}; )` — the repetition can contain other tokens
201/// - `$( $k => println!("{}", $!v), )` — even multiple interpolations
202#[proc_macro]
203pub fn code(pattern: TokenStream) -> TokenStream {
204    let pattern = pattern.to_string();
205    let mut scan = unscanny::Scanner::new(&pattern);
206    scan.eat_whitespace();
207    let out = scan.eat_while(id_continue);
208    scan.eat_whitespace();
209    scan.expect("=>");
210    scan.eat_whitespace();
211
212    let mut s = String::new();
213    s.push('{');
214    gen_recursive(&mut scan, &mut s, out);
215    s.push('}');
216    s.parse().unwrap()
217}