osc_router/
lib.rs

1//! OSC spec: <https://opensoundcontrol.stanford.edu/spec-1_0.html>
2
3mod logic;
4
5use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
6
7#[proc_macro]
8pub fn osc(ts: TokenStream) -> TokenStream {
9    let FnDecl {
10        attributes,
11        fn_name,
12        generics,
13        mut arg_stream,
14        body,
15    } = parse_function_declaration(ts);
16    let hierarchy: logic::Hierarchy = logic::stratify(body);
17    let parser = hierarchy.parser();
18
19    let mut acc: TokenStream = TokenStream::from_iter(attributes);
20    let () = acc.extend([
21        TokenTree::Ident(Ident::new("async", Span::call_site())),
22        TokenTree::Ident(Ident::new("fn", Span::call_site())),
23        TokenTree::Ident(fn_name),
24        TokenTree::Punct(Punct::new('<', Spacing::Alone)),
25    ]);
26    if !generics.is_empty() {
27        let () = acc.extend(generics);
28        let () = acc.extend(core::iter::once(TokenTree::Punct(Punct::new(
29            ',',
30            Spacing::Alone,
31        ))));
32    }
33    let () = acc.extend([
34        TokenTree::Ident(Ident::new("AsyncRestart", Span::call_site())),
35        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
36        TokenTree::Ident(Ident::new("Future", Span::call_site())),
37        TokenTree::Punct(Punct::new('<', Spacing::Alone)),
38        TokenTree::Ident(Ident::new("Output", Span::call_site())),
39        TokenTree::Punct(Punct::new('=', Spacing::Alone)),
40        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
41        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
42        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
43        TokenTree::Ident(Ident::new("AsyncRecvByte", Span::call_site())),
44        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
45        TokenTree::Ident(Ident::new("Future", Span::call_site())),
46        TokenTree::Punct(Punct::new('<', Spacing::Alone)),
47        TokenTree::Ident(Ident::new("Output", Span::call_site())),
48        TokenTree::Punct(Punct::new('=', Spacing::Alone)),
49        TokenTree::Ident(Ident::new("u8", Span::call_site())),
50        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
51        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
52        TokenTree::Ident(Ident::new("AsyncSendByte", Span::call_site())),
53        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
54        TokenTree::Ident(Ident::new("Future", Span::call_site())),
55        TokenTree::Punct(Punct::new('<', Spacing::Alone)),
56        TokenTree::Ident(Ident::new("Output", Span::call_site())),
57        TokenTree::Punct(Punct::new('=', Spacing::Alone)),
58        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
59        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
60        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
61        TokenTree::Ident(Ident::new("AsyncError", Span::call_site())),
62        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
63        TokenTree::Ident(Ident::new("Future", Span::call_site())),
64        TokenTree::Punct(Punct::new('<', Spacing::Alone)),
65        TokenTree::Ident(Ident::new("Output", Span::call_site())),
66        TokenTree::Punct(Punct::new('=', Spacing::Alone)),
67        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
68        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
69        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
70        TokenTree::Ident(Ident::new("Restart", Span::call_site())),
71        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
72        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
73        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
74        TokenTree::Ident(Ident::new("core", Span::call_site())),
75        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
76        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
77        TokenTree::Ident(Ident::new("ops", Span::call_site())),
78        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
79        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
80        TokenTree::Ident(Ident::new("FnMut", Span::call_site())),
81        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
82        TokenTree::Punct(Punct::new('-', Spacing::Joint)),
83        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
84        TokenTree::Ident(Ident::new("AsyncRestart", Span::call_site())),
85        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
86        TokenTree::Ident(Ident::new("RecvByte", Span::call_site())),
87        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
88        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
89        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
90        TokenTree::Ident(Ident::new("core", Span::call_site())),
91        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
92        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
93        TokenTree::Ident(Ident::new("ops", Span::call_site())),
94        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
95        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
96        TokenTree::Ident(Ident::new("FnMut", Span::call_site())),
97        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
98        TokenTree::Punct(Punct::new('-', Spacing::Joint)),
99        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
100        TokenTree::Ident(Ident::new("AsyncRecvByte", Span::call_site())),
101        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
102        TokenTree::Ident(Ident::new("SendByte", Span::call_site())),
103        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
104        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
105        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
106        TokenTree::Ident(Ident::new("core", Span::call_site())),
107        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
108        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
109        TokenTree::Ident(Ident::new("ops", Span::call_site())),
110        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
111        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
112        TokenTree::Ident(Ident::new("FnMut", Span::call_site())),
113        TokenTree::Group(Group::new(
114            Delimiter::Parenthesis,
115            TokenStream::from_iter(core::iter::once(TokenTree::Ident(Ident::new(
116                "u8",
117                Span::call_site(),
118            )))),
119        )),
120        TokenTree::Punct(Punct::new('-', Spacing::Joint)),
121        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
122        TokenTree::Ident(Ident::new("AsyncSendByte", Span::call_site())),
123        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
124        TokenTree::Ident(Ident::new("Error", Span::call_site())),
125        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
126        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
127        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
128        TokenTree::Ident(Ident::new("core", Span::call_site())),
129        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
130        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
131        TokenTree::Ident(Ident::new("ops", Span::call_site())),
132        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
133        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
134        TokenTree::Ident(Ident::new("FnMut", Span::call_site())),
135        TokenTree::Group(Group::new(
136            Delimiter::Parenthesis,
137            TokenStream::from_iter([
138                TokenTree::Punct(Punct::new('&', Spacing::Alone)),
139                TokenTree::Punct(Punct::new('\'', Spacing::Joint)),
140                TokenTree::Ident(Ident::new("static", Span::call_site())),
141                TokenTree::Ident(Ident::new("str", Span::call_site())),
142                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
143                TokenTree::Ident(Ident::new("u8", Span::call_site())),
144            ]),
145        )),
146        TokenTree::Punct(Punct::new('-', Spacing::Joint)),
147        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
148        TokenTree::Ident(Ident::new("AsyncError", Span::call_site())),
149        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
150        TokenTree::Group(Group::new(Delimiter::Parenthesis, {
151            if !arg_stream.is_empty() {
152                let () = arg_stream.extend(core::iter::once(TokenTree::Punct(Punct::new(
153                    ',',
154                    Spacing::Alone,
155                ))));
156            }
157            let () = arg_stream.extend([
158                TokenTree::Punct(Punct::new(':', Spacing::Joint)),
159                TokenTree::Punct(Punct::new(':', Spacing::Alone)),
160                TokenTree::Ident(Ident::new("osc_router_traits", Span::call_site())),
161                TokenTree::Punct(Punct::new(':', Spacing::Joint)),
162                TokenTree::Punct(Punct::new(':', Spacing::Alone)),
163                TokenTree::Ident(Ident::new("Driver", Span::call_site())),
164                TokenTree::Group(Group::new(
165                    Delimiter::Brace,
166                    TokenStream::from_iter([
167                        TokenTree::Ident(Ident::new("mut", Span::call_site())),
168                        TokenTree::Ident(Ident::new("restart", Span::call_site())),
169                        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
170                        TokenTree::Ident(Ident::new("mut", Span::call_site())),
171                        TokenTree::Ident(Ident::new("recv_byte", Span::call_site())),
172                        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
173                        TokenTree::Ident(Ident::new("mut", Span::call_site())),
174                        TokenTree::Ident(Ident::new("send_byte", Span::call_site())),
175                        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
176                        TokenTree::Ident(Ident::new("mut", Span::call_site())),
177                        TokenTree::Ident(Ident::new("error", Span::call_site())),
178                    ]),
179                )),
180                TokenTree::Punct(Punct::new(':', Spacing::Alone)),
181                TokenTree::Punct(Punct::new(':', Spacing::Joint)),
182                TokenTree::Punct(Punct::new(':', Spacing::Alone)),
183                TokenTree::Ident(Ident::new("osc_router_traits", Span::call_site())),
184                TokenTree::Punct(Punct::new(':', Spacing::Joint)),
185                TokenTree::Punct(Punct::new(':', Spacing::Alone)),
186                TokenTree::Ident(Ident::new("Driver", Span::call_site())),
187                TokenTree::Punct(Punct::new('<', Spacing::Alone)),
188                TokenTree::Ident(Ident::new("AsyncRestart", Span::call_site())),
189                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
190                TokenTree::Ident(Ident::new("AsyncRecvByte", Span::call_site())),
191                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
192                TokenTree::Ident(Ident::new("AsyncSendByte", Span::call_site())),
193                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
194                TokenTree::Ident(Ident::new("AsyncError", Span::call_site())),
195                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
196                TokenTree::Ident(Ident::new("Restart", Span::call_site())),
197                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
198                TokenTree::Ident(Ident::new("RecvByte", Span::call_site())),
199                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
200                TokenTree::Ident(Ident::new("SendByte", Span::call_site())),
201                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
202                TokenTree::Ident(Ident::new("Error", Span::call_site())),
203                TokenTree::Punct(Punct::new('>', Spacing::Alone)),
204            ]);
205            arg_stream
206        })),
207        TokenTree::Punct(Punct::new('-', Spacing::Joint)),
208        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
209        TokenTree::Punct(Punct::new('!', Spacing::Alone)),
210        TokenTree::Group(Group::new(
211            Delimiter::Brace,
212            TokenStream::from_iter([
213                TokenTree::Ident(Ident::new("loop", Span::call_site())),
214                TokenTree::Group(Group::new(
215                    Delimiter::Brace,
216                    TokenStream::from_iter(
217                        [
218                            TokenTree::Ident(Ident::new("let", Span::call_site())),
219                            TokenTree::Group(Group::new(
220                                Delimiter::Parenthesis,
221                                TokenStream::new(),
222                            )),
223                            TokenTree::Punct(Punct::new('=', Spacing::Alone)),
224                            TokenTree::Ident(Ident::new("restart", Span::call_site())),
225                            TokenTree::Group(Group::new(
226                                Delimiter::Parenthesis,
227                                TokenStream::new(),
228                            )),
229                            TokenTree::Punct(Punct::new('.', Spacing::Alone)),
230                            TokenTree::Ident(Ident::new("await", Span::call_site())),
231                            TokenTree::Punct(Punct::new(';', Spacing::Alone)),
232                        ]
233                        .into_iter()
234                        .chain(parser.into_tokens()),
235                    ),
236                )),
237            ]),
238        )),
239    ]);
240    acc
241}
242
243#[derive(Debug)]
244struct FnDecl {
245    pub attributes: Vec<TokenTree>,
246    pub fn_name: Ident,
247    pub generics: Vec<TokenTree>,
248    pub arg_stream: TokenStream,
249    pub body: TokenStream,
250}
251
252#[inline]
253fn parse_function_declaration(iter: impl IntoIterator<Item = TokenTree>) -> FnDecl {
254    let mut iter = iter.into_iter();
255
256    let mut attributes = vec![];
257
258    // Iterate through all the attribute macros (`#[...]`):
259    'attributes: loop {
260        match iter.next() {
261            None => panic!(
262                "Expected a function declaration in an OSC router macro, but the macro body ended"
263            ),
264            Some(TokenTree::Punct(punct)) => match punct.as_char() {
265                '#' => match iter.next() {
266                    None => panic!(
267                        "Expected a function declaration in an OSC router macro, but the macro body ended after a hashtag"
268                    ),
269                    Some(TokenTree::Group(group)) => match group.delimiter() {
270                        Delimiter::Bracket => {
271                            let () = attributes.push(TokenTree::Punct(punct));
272                            let () = attributes.push(TokenTree::Group(group));
273                        }
274                        other => panic!(
275                            "Expected an attribute body (`[...]`) after a hashtag in an OSC router macro but found a body enclosed in {other:#?} tokens"
276                        ),
277                    },
278                    Some(other) => panic!(
279                        "Expected an attribute body (`[...]`) after a hashtag in an OSC router macro but found {other:#?}"
280                    ),
281                },
282                c => panic!("Unrecognized punctuation in an OSC router macro: {c:#?}"),
283            },
284            Some(TokenTree::Ident(ident)) => match format!("{ident}").as_str() {
285                "pub" => {
286                    let () = attributes.push(TokenTree::Ident(ident));
287                }
288                "async" => break 'attributes,
289                _ => panic!(
290                    "Expected `pub` or `async` to begin the function declaration in an OSC router macro but found {ident:#?}"
291                ),
292            },
293            Some(other) => {
294                panic!(
295                    "Expected a function declaration in an OSC router macro but found {other:#?}"
296                )
297            }
298        }
299    }
300
301    {
302        // Expecting `fn`:
303        let Some(tree) = iter.next() else {
304            panic!("Expected `fn` after `async` in an OSC router macro but the macro body ended")
305        };
306        let TokenTree::Ident(ident) = tree else {
307            panic!("Expected `fn` after `async` in an OSC router macro but found {tree:#?}")
308        };
309        if !matches!(format!("{ident}").as_str(), "fn") {
310            panic!("Expected `fn` after `async` in an OSC router macro but found {ident:#?}")
311        }
312    }
313
314    let fn_name = {
315        // Expecting the function name:
316        let Some(tree) = iter.next() else {
317            panic!(
318                "Expected a function name after `fn` in an OSC router macro but the macro body ended"
319            )
320        };
321        let TokenTree::Ident(ident) = tree else {
322            panic!("Expected a function name after `fn` in an OSC router macro but found {tree:#?}")
323        };
324        ident
325    };
326
327    let mut generics = vec![];
328    let arg_stream = {
329        // Expecting parenthesized arguments (but no arguments, so really just `()`):
330        let Some(mut tree) = iter.next() else {
331            panic!(
332                "Expected arguments after the function name in an OSC router macro but the macro body ended"
333            )
334        };
335        if let TokenTree::Punct(ref punct) = tree {
336            if !matches!(punct.as_char(), '<') {
337                panic!(
338                    "Expected arguments after the function name in an OSC router macro but found {tree:#?}"
339                )
340            }
341            let mut angle_bracket_inception: usize = 1;
342            'generics: loop {
343                tree = iter.next().expect(
344                        "Function generics in an OSC router macro missing a closing `>` (maybe off-by-one error?)"
345                );
346                if let TokenTree::Punct(ref punct) = tree {
347                    match punct.as_char() {
348                        '<' => angle_bracket_inception += 1,
349                        '>' => {
350                            angle_bracket_inception -= 1;
351                            if angle_bracket_inception == 0 {
352                                tree = iter.next().expect(
353                                    "Function generics in an OSC router macro missing a closing `>` (maybe off-by-one error?)"
354                                );
355                                break 'generics;
356                            }
357                        }
358                        _ => {}
359                    }
360                }
361                let () = generics.push(tree);
362            }
363        }
364        let TokenTree::Group(group) = tree else {
365            panic!(
366                "Expected arguments after the function name in an OSC router macro but found {tree:#?}"
367            )
368        };
369        group.stream()
370    };
371
372    {
373        let Some(tree) = iter.next() else {
374            panic!(
375                "Expected `->` after function arguments in an OSC router macro but the macro body ended"
376            )
377        };
378        let TokenTree::Punct(punct) = tree else {
379            panic!(
380                "Expected `->` after function arguments in an OSC router macro but found {tree:#?}"
381            )
382        };
383        if !matches!(punct.as_char(), '-') {
384            panic!(
385                "Expected `->` after function arguments in an OSC router macro but found {punct:#?}"
386            )
387        }
388        if !matches!(punct.spacing(), Spacing::Joint) {
389            panic!(
390                "Expected `->` after function arguments in an OSC router macro but found {punct:#?}"
391            )
392        }
393    }
394
395    {
396        let Some(tree) = iter.next() else {
397            panic!(
398                "Expected `->` after function arguments in an OSC router macro but the macro body ended"
399            )
400        };
401        let TokenTree::Punct(punct) = tree else {
402            panic!(
403                "Expected `->` after function arguments in an OSC router macro but found `-` and then {tree:#?}"
404            )
405        };
406        if !matches!(punct.as_char(), '>') {
407            panic!(
408                "Expected `->` after function arguments in an OSC router macro but found `-` and then {punct:#?}"
409            )
410        }
411        if !matches!(punct.spacing(), Spacing::Alone) {
412            panic!(
413                "Expected `->` after function arguments in an OSC router macro but found `-` and then {punct:#?}"
414            )
415        }
416    }
417
418    {
419        let Some(tree) = iter.next() else {
420            panic!(
421                "Expected `!` as the return type of the function in an OSC router macro but the macro body ended"
422            )
423        };
424        let TokenTree::Punct(punct) = tree else {
425            panic!(
426                "Expected `!` as the return type of the function in an OSC router macro but found {tree:#?}"
427            )
428        };
429        if !matches!(punct.as_char(), '!') {
430            panic!(
431                "Expected `!` as the return type of the function in an OSC router macro but found {punct:#?}"
432            )
433        }
434        if !matches!(punct.spacing(), Spacing::Alone) {
435            panic!(
436                "Expected `!` as the return type of the function in an OSC router macro but found {punct:#?}"
437            )
438        }
439    }
440
441    let body = {
442        let Some(tree) = iter.next() else {
443            panic!(
444                "Expected a function body after `-> !` in an OSC router macro but the macro body ended"
445            )
446        };
447        let TokenTree::Group(group) = tree else {
448            panic!(
449                "Expected a function body after `-> !` in an OSC router macro but found {tree:#?}"
450            )
451        };
452        if !matches!(group.delimiter(), Delimiter::Brace) {
453            panic!(
454                "Expected a function body after `-> !` in an OSC router macro but found {group:#?}"
455            )
456        }
457        group.stream()
458    };
459
460    if let Some(extra) = iter.next() {
461        panic!("Expected the OSC router macro to end after a single function but found {extra:#?}");
462    }
463
464    FnDecl {
465        attributes,
466        fn_name,
467        generics,
468        arg_stream,
469        body,
470    }
471}