rtic_macros/syntax/
parse.rs

1mod app;
2mod hardware_task;
3mod idle;
4mod init;
5mod resource;
6mod software_task;
7mod util;
8
9use proc_macro2::TokenStream as TokenStream2;
10use syn::{
11    braced,
12    parse::{self, Parse, ParseStream, Parser},
13    token::Brace,
14    Ident, Item, LitInt, Token,
15};
16
17use crate::syntax::{
18    ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal},
19    Either,
20};
21
22// Parse the app, both app arguments and body (input)
23pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result<App> {
24    let args = AppArgs::parse(args)?;
25    let input: Input = syn::parse2(input)?;
26
27    App::parse(args, input)
28}
29
30pub(crate) struct Input {
31    _mod_token: Token![mod],
32    pub ident: Ident,
33    _brace_token: Brace,
34    pub items: Vec<Item>,
35}
36
37impl Parse for Input {
38    fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
39        fn parse_items(input: ParseStream<'_>) -> parse::Result<Vec<Item>> {
40            let mut items = vec![];
41
42            while !input.is_empty() {
43                items.push(input.parse()?);
44            }
45
46            Ok(items)
47        }
48
49        let content;
50
51        let _mod_token = input.parse()?;
52        let ident = input.parse()?;
53        let _brace_token = braced!(content in input);
54        let items = content.call(parse_items)?;
55
56        Ok(Input {
57            _mod_token,
58            ident,
59            _brace_token,
60            items,
61        })
62    }
63}
64
65fn init_args(tokens: TokenStream2) -> parse::Result<InitArgs> {
66    (|input: ParseStream<'_>| -> parse::Result<InitArgs> {
67        if input.is_empty() {
68            return Ok(InitArgs::default());
69        }
70
71        let mut local_resources = None;
72
73        if !input.is_empty() {
74            loop {
75                // Parse identifier name
76                let ident: Ident = input.parse()?;
77                // Handle equal sign
78                let _: Token![=] = input.parse()?;
79
80                match &*ident.to_string() {
81                    "local" => {
82                        if local_resources.is_some() {
83                            return Err(parse::Error::new(
84                                ident.span(),
85                                "argument appears more than once",
86                            ));
87                        }
88
89                        local_resources = Some(util::parse_local_resources(input)?);
90                    }
91                    _ => {
92                        return Err(parse::Error::new(ident.span(), "unexpected argument"));
93                    }
94                }
95
96                if input.is_empty() {
97                    break;
98                }
99                // Handle comma: ,
100                let _: Token![,] = input.parse()?;
101            }
102        }
103
104        if let Some(locals) = &local_resources {
105            for (ident, task_local) in locals {
106                if let TaskLocal::External = task_local {
107                    return Err(parse::Error::new(
108                        ident.span(),
109                        "only declared local resources are allowed in init",
110                    ));
111                }
112            }
113        }
114
115        Ok(InitArgs {
116            local_resources: local_resources.unwrap_or_default(),
117        })
118    })
119    .parse2(tokens)
120}
121
122fn idle_args(tokens: TokenStream2) -> parse::Result<IdleArgs> {
123    (|input: ParseStream<'_>| -> parse::Result<IdleArgs> {
124        if input.is_empty() {
125            return Ok(IdleArgs::default());
126        }
127
128        let mut shared_resources = None;
129        let mut local_resources = None;
130
131        if !input.is_empty() {
132            loop {
133                // Parse identifier name
134                let ident: Ident = input.parse()?;
135                // Handle equal sign
136                let _: Token![=] = input.parse()?;
137
138                match &*ident.to_string() {
139                    "shared" => {
140                        if shared_resources.is_some() {
141                            return Err(parse::Error::new(
142                                ident.span(),
143                                "argument appears more than once",
144                            ));
145                        }
146
147                        shared_resources = Some(util::parse_shared_resources(input)?);
148                    }
149
150                    "local" => {
151                        if local_resources.is_some() {
152                            return Err(parse::Error::new(
153                                ident.span(),
154                                "argument appears more than once",
155                            ));
156                        }
157
158                        local_resources = Some(util::parse_local_resources(input)?);
159                    }
160
161                    _ => {
162                        return Err(parse::Error::new(ident.span(), "unexpected argument"));
163                    }
164                }
165                if input.is_empty() {
166                    break;
167                }
168
169                // Handle comma: ,
170                let _: Token![,] = input.parse()?;
171            }
172        }
173
174        Ok(IdleArgs {
175            shared_resources: shared_resources.unwrap_or_default(),
176            local_resources: local_resources.unwrap_or_default(),
177        })
178    })
179    .parse2(tokens)
180}
181
182fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
183    (|input: ParseStream<'_>| -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
184        if input.is_empty() {
185            return Ok(Either::Right(SoftwareTaskArgs::default()));
186        }
187
188        let mut binds = None;
189        let mut priority = None;
190        let mut shared_resources = None;
191        let mut local_resources = None;
192        let mut prio_span = None;
193
194        loop {
195            if input.is_empty() {
196                break;
197            }
198
199            // Parse identifier name
200            let ident: Ident = input.parse()?;
201            let ident_s = ident.to_string();
202
203            // Handle equal sign
204            let _: Token![=] = input.parse()?;
205
206            match &*ident_s {
207                "binds" => {
208                    if binds.is_some() {
209                        return Err(parse::Error::new(
210                            ident.span(),
211                            "argument appears more than once",
212                        ));
213                    }
214
215                    // Parse identifier name
216                    let ident = input.parse()?;
217
218                    binds = Some(ident);
219                }
220
221                "priority" => {
222                    if priority.is_some() {
223                        return Err(parse::Error::new(
224                            ident.span(),
225                            "argument appears more than once",
226                        ));
227                    }
228
229                    // #lit
230                    let lit: LitInt = input.parse()?;
231
232                    if !lit.suffix().is_empty() {
233                        return Err(parse::Error::new(
234                            lit.span(),
235                            "this literal must be unsuffixed",
236                        ));
237                    }
238
239                    let value = lit.base10_parse::<u8>().ok();
240                    if value.is_none() {
241                        return Err(parse::Error::new(
242                            lit.span(),
243                            "this literal must be in the range 0...255",
244                        ));
245                    }
246
247                    prio_span = Some(lit.span());
248                    priority = Some(value.unwrap());
249                }
250
251                "shared" => {
252                    if shared_resources.is_some() {
253                        return Err(parse::Error::new(
254                            ident.span(),
255                            "argument appears more than once",
256                        ));
257                    }
258
259                    shared_resources = Some(util::parse_shared_resources(input)?);
260                }
261
262                "local" => {
263                    if local_resources.is_some() {
264                        return Err(parse::Error::new(
265                            ident.span(),
266                            "argument appears more than once",
267                        ));
268                    }
269
270                    local_resources = Some(util::parse_local_resources(input)?);
271                }
272
273                _ => {
274                    return Err(parse::Error::new(ident.span(), "unexpected argument"));
275                }
276            }
277
278            if input.is_empty() {
279                break;
280            }
281
282            // Handle comma: ,
283            let _: Token![,] = input.parse()?;
284        }
285        let shared_resources = shared_resources.unwrap_or_default();
286        let local_resources = local_resources.unwrap_or_default();
287
288        Ok(if let Some(binds) = binds {
289            // Hardware tasks can't run at anything lower than 1
290            let priority = priority.unwrap_or(1);
291
292            if priority == 0 {
293                return Err(parse::Error::new(
294                    prio_span.unwrap(),
295                    "hardware tasks are not allowed to be at priority 0",
296                ));
297            }
298
299            Either::Left(HardwareTaskArgs {
300                binds,
301                priority,
302                shared_resources,
303                local_resources,
304            })
305        } else {
306            // Software tasks start at idle priority
307            let priority = priority.unwrap_or(0);
308
309            Either::Right(SoftwareTaskArgs {
310                priority,
311                shared_resources,
312                local_resources,
313            })
314        })
315    })
316    .parse2(tokens)
317}