rtic_syntax/
parse.rs

1mod app;
2mod hardware_task;
3mod idle;
4mod init;
5mod monotonic;
6mod resource;
7mod software_task;
8mod util;
9
10use proc_macro2::TokenStream as TokenStream2;
11use syn::{
12    braced, parenthesized,
13    parse::{self, Parse, ParseStream, Parser},
14    token::{self, Brace},
15    Ident, Item, LitBool, LitInt, Path, Token,
16};
17
18use crate::{
19    ast::{
20        App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, MonotonicArgs, SoftwareTaskArgs,
21        TaskLocal,
22    },
23    Either, Settings,
24};
25
26// Parse the app, both app arguments and body (input)
27pub fn app(args: TokenStream2, input: TokenStream2, settings: &Settings) -> parse::Result<App> {
28    let args = AppArgs::parse(args)?;
29    let input: Input = syn::parse2(input)?;
30
31    App::parse(args, input, settings)
32}
33
34pub(crate) struct Input {
35    _mod_token: Token![mod],
36    pub ident: Ident,
37    _brace_token: Brace,
38    pub items: Vec<Item>,
39}
40
41impl Parse for Input {
42    fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
43        fn parse_items(input: ParseStream<'_>) -> parse::Result<Vec<Item>> {
44            let mut items = vec![];
45
46            while !input.is_empty() {
47                items.push(input.parse()?);
48            }
49
50            Ok(items)
51        }
52
53        let content;
54
55        let _mod_token = input.parse()?;
56        let ident = input.parse()?;
57        let _brace_token = braced!(content in input);
58        let items = content.call(parse_items)?;
59
60        Ok(Input {
61            _mod_token,
62            ident,
63            _brace_token,
64            items,
65        })
66    }
67}
68
69fn init_args(tokens: TokenStream2) -> parse::Result<InitArgs> {
70    (|input: ParseStream<'_>| -> parse::Result<InitArgs> {
71        if input.is_empty() {
72            return Ok(InitArgs::default());
73        }
74
75        let mut local_resources = None;
76
77        let content;
78        parenthesized!(content in input);
79
80        if !content.is_empty() {
81            loop {
82                // Parse identifier name
83                let ident: Ident = content.parse()?;
84                // Handle equal sign
85                let _: Token![=] = content.parse()?;
86
87                match &*ident.to_string() {
88                    "local" => {
89                        if local_resources.is_some() {
90                            return Err(parse::Error::new(
91                                ident.span(),
92                                "argument appears more than once",
93                            ));
94                        }
95
96                        local_resources = Some(util::parse_local_resources(&content)?);
97                    }
98                    _ => {
99                        return Err(parse::Error::new(ident.span(), "unexpected argument"));
100                    }
101                }
102
103                if content.is_empty() {
104                    break;
105                }
106                // Handle comma: ,
107                let _: Token![,] = content.parse()?;
108            }
109        }
110
111        if let Some(locals) = &local_resources {
112            for (ident, task_local) in locals {
113                if let TaskLocal::External = task_local {
114                    return Err(parse::Error::new(
115                        ident.span(),
116                        "only declared local resources are allowed in init",
117                    ));
118                }
119            }
120        }
121
122        Ok(InitArgs {
123            local_resources: local_resources.unwrap_or_default(),
124        })
125    })
126    .parse2(tokens)
127}
128
129fn idle_args(tokens: TokenStream2) -> parse::Result<IdleArgs> {
130    (|input: ParseStream<'_>| -> parse::Result<IdleArgs> {
131        if input.is_empty() {
132            return Ok(IdleArgs::default());
133        }
134
135        let mut shared_resources = None;
136        let mut local_resources = None;
137
138        let content;
139        parenthesized!(content in input);
140        if !content.is_empty() {
141            loop {
142                // Parse identifier name
143                let ident: Ident = content.parse()?;
144                // Handle equal sign
145                let _: Token![=] = content.parse()?;
146
147                match &*ident.to_string() {
148                    "shared" => {
149                        if shared_resources.is_some() {
150                            return Err(parse::Error::new(
151                                ident.span(),
152                                "argument appears more than once",
153                            ));
154                        }
155
156                        shared_resources = Some(util::parse_shared_resources(&content)?);
157                    }
158
159                    "local" => {
160                        if local_resources.is_some() {
161                            return Err(parse::Error::new(
162                                ident.span(),
163                                "argument appears more than once",
164                            ));
165                        }
166
167                        local_resources = Some(util::parse_local_resources(&content)?);
168                    }
169
170                    _ => {
171                        return Err(parse::Error::new(ident.span(), "unexpected argument"));
172                    }
173                }
174                if content.is_empty() {
175                    break;
176                }
177
178                // Handle comma: ,
179                let _: Token![,] = content.parse()?;
180            }
181        }
182
183        Ok(IdleArgs {
184            shared_resources: shared_resources.unwrap_or_default(),
185            local_resources: local_resources.unwrap_or_default(),
186        })
187    })
188    .parse2(tokens)
189}
190
191fn task_args(
192    tokens: TokenStream2,
193    settings: &Settings,
194) -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
195    (|input: ParseStream<'_>| -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
196        if input.is_empty() {
197            return Ok(Either::Right(SoftwareTaskArgs::default()));
198        }
199
200        let mut binds = None;
201        let mut capacity = None;
202        let mut priority = None;
203        let mut shared_resources = None;
204        let mut local_resources = None;
205
206
207        let content;
208        parenthesized!(content in input);
209        loop {
210            if content.is_empty() {
211                break;
212            }
213
214            // Parse identifier name
215            let ident: Ident = content.parse()?;
216            // Handle equal sign
217            let _: Token![=] = content.parse()?;
218
219            let ident_s = ident.to_string();
220            match &*ident_s {
221                "binds" if !settings.parse_binds => {
222                    return Err(parse::Error::new(
223                        ident.span(),
224                        "Unexpected bind in task argument. Binds are only parsed if Settings::parse_binds is set.",
225                    ));
226                }
227
228                "binds" if settings.parse_binds => {
229                    if binds.is_some() {
230                        return Err(parse::Error::new(
231                            ident.span(),
232                            "argument appears more than once",
233                        ));
234                    }
235
236                    if capacity.is_some() {
237                        return Err(parse::Error::new(
238                            ident.span(),
239                            "hardware tasks can't use the `capacity` argument",
240                        ));
241                    }
242
243                    // Parse identifier name
244                    let ident = content.parse()?;
245
246                    binds = Some(ident);
247                }
248
249                "capacity" => {
250                    if capacity.is_some() {
251                        return Err(parse::Error::new(
252                            ident.span(),
253                            "argument appears more than once",
254                        ));
255                    }
256
257                    if binds.is_some() {
258                        return Err(parse::Error::new(
259                            ident.span(),
260                            "hardware tasks can't use the `capacity` argument",
261                        ));
262                    }
263
264                    // #lit
265                    let lit: LitInt = content.parse()?;
266
267                    if !lit.suffix().is_empty() {
268                        return Err(parse::Error::new(
269                            lit.span(),
270                            "this literal must be unsuffixed",
271                        ));
272                    }
273
274                    let value = lit.base10_parse::<u8>().ok();
275                    if value.is_none() || value == Some(0) {
276                        return Err(parse::Error::new(
277                            lit.span(),
278                            "this literal must be in the range 1...255",
279                        ));
280                    }
281
282                    capacity = Some(value.unwrap());
283                }
284
285                "priority" => {
286                    if priority.is_some() {
287                        return Err(parse::Error::new(
288                            ident.span(),
289                            "argument appears more than once",
290                        ));
291                    }
292
293                    // #lit
294                    let lit: LitInt = content.parse()?;
295
296                    if !lit.suffix().is_empty() {
297                        return Err(parse::Error::new(
298                            lit.span(),
299                            "this literal must be unsuffixed",
300                        ));
301                    }
302
303                    let value = lit.base10_parse::<u8>().ok();
304                    if value.is_none() || value == Some(0) {
305                        return Err(parse::Error::new(
306                            lit.span(),
307                            "this literal must be in the range 1...255",
308                        ));
309                    }
310
311                    priority = Some(value.unwrap());
312                }
313
314                "shared" => {
315                    if shared_resources.is_some() {
316                        return Err(parse::Error::new(
317                            ident.span(),
318                            "argument appears more than once",
319                        ));
320                    }
321
322                    shared_resources = Some(util::parse_shared_resources(&content)?);
323                }
324
325                "local" => {
326                    if local_resources.is_some() {
327                        return Err(parse::Error::new(
328                            ident.span(),
329                            "argument appears more than once",
330                        ));
331                    }
332
333                    local_resources = Some(util::parse_local_resources(&content)?);
334                }
335
336                _ => {
337                    return Err(parse::Error::new(ident.span(), "unexpected argument"));
338                }
339            }
340
341            if content.is_empty() {
342                break;
343            }
344
345            // Handle comma: ,
346            let _: Token![,] = content.parse()?;
347        }
348        let priority = priority.unwrap_or(1);
349        let shared_resources = shared_resources.unwrap_or_default();
350        let local_resources = local_resources.unwrap_or_default();
351
352        Ok(if let Some(binds) = binds {
353            Either::Left(HardwareTaskArgs {
354                binds,
355                priority,
356                shared_resources,
357                local_resources,
358            })
359        } else {
360            Either::Right(SoftwareTaskArgs {
361                capacity: capacity.unwrap_or(1),
362                priority,
363                shared_resources,
364                local_resources,
365            })
366        })
367    })
368    .parse2(tokens)
369}
370
371fn monotonic_args(path: Path, tokens: TokenStream2) -> parse::Result<MonotonicArgs> {
372    (|input: ParseStream<'_>| -> parse::Result<MonotonicArgs> {
373        let mut binds = None;
374        let mut priority = None;
375        let mut default = None;
376
377        if !input.peek(token::Paren) {
378            return Err(parse::Error::new(
379                path.segments.first().unwrap().ident.span(),
380                "expected opening ( in #[monotonic( ... )]",
381            ));
382        }
383
384        let content;
385        parenthesized!(content in input);
386
387        if !content.is_empty() {
388            loop {
389                // Parse identifier name
390                let ident: Ident = content.parse()?;
391                // Handle equal sign
392                let _: Token![=] = content.parse()?;
393
394                match &*ident.to_string() {
395                    "binds" => {
396                        if binds.is_some() {
397                            return Err(parse::Error::new(
398                                ident.span(),
399                                "argument appears more than once",
400                            ));
401                        }
402                        // Parse identifier name
403                        let ident = content.parse()?;
404
405                        binds = Some(ident);
406                    }
407
408                    "priority" => {
409                        if priority.is_some() {
410                            return Err(parse::Error::new(
411                                ident.span(),
412                                "argument appears more than once",
413                            ));
414                        }
415
416                        // #lit
417                        let lit: LitInt = content.parse()?;
418
419                        if !lit.suffix().is_empty() {
420                            return Err(parse::Error::new(
421                                lit.span(),
422                                "this literal must be unsuffixed",
423                            ));
424                        }
425
426                        let value = lit.base10_parse::<u8>().ok();
427                        if value.is_none() || value == Some(0) {
428                            return Err(parse::Error::new(
429                                lit.span(),
430                                "this literal must be in the range 1...255",
431                            ));
432                        }
433
434                        priority = Some(value.unwrap());
435                    }
436
437                    "default" => {
438                        if default.is_some() {
439                            return Err(parse::Error::new(
440                                ident.span(),
441                                "argument appears more than once",
442                            ));
443                        }
444
445                        let lit: LitBool = content.parse()?;
446                        default = Some(lit.value);
447                    }
448
449                    _ => {
450                        return Err(parse::Error::new(ident.span(), "unexpected argument"));
451                    }
452                }
453                if content.is_empty() {
454                    break;
455                }
456
457                // Handle comma: ,
458                let _: Token![,] = content.parse()?;
459            }
460        }
461
462        let binds = if let Some(r) = binds {
463            r
464        } else {
465            return Err(parse::Error::new(
466                content.span(),
467                "`binds = ...` is missing",
468            ));
469        };
470        let default = default.unwrap_or(false);
471
472        Ok(MonotonicArgs {
473            binds,
474            priority,
475            default,
476        })
477    })
478    .parse2(tokens)
479}