rtic_macros/syntax/parse/
app.rs

1use std::collections::HashSet;
2
3// use indexmap::map::Entry;
4use proc_macro2::TokenStream as TokenStream2;
5use syn::{
6    parse::{self, ParseStream, Parser},
7    spanned::Spanned,
8    Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Visibility,
9};
10
11use crate::syntax::{
12    ast::{
13        App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs,
14        LocalResource, SharedResource, SoftwareTask,
15    },
16    backend::BackendArgs,
17    parse::{self as syntax_parse, util},
18    Either, Map, Set,
19};
20
21use super::Input;
22
23impl AppArgs {
24    pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
25        (|input: ParseStream<'_>| -> parse::Result<Self> {
26            let mut custom = Set::new();
27            let mut device = None;
28            let mut core = true;
29            let mut peripherals = true;
30            let mut dispatchers = Dispatchers::new();
31            let mut backend = None;
32
33            loop {
34                if input.is_empty() {
35                    break;
36                }
37
38                // #ident = ..
39                let ident: Ident = input.parse()?;
40                let _eq_token: Token![=] = input.parse()?;
41
42                if custom.contains(&ident) {
43                    return Err(parse::Error::new(
44                        ident.span(),
45                        "argument appears more than once",
46                    ));
47                }
48
49                custom.insert(ident.clone());
50
51                let ks = ident.to_string();
52
53                match &*ks {
54                    "device" => {
55                        if let Ok(p) = input.parse::<Path>() {
56                            device = Some(p);
57                        } else {
58                            return Err(parse::Error::new(
59                                ident.span(),
60                                "unexpected argument value; this should be a path",
61                            ));
62                        }
63                    }
64
65                    "core" => {
66                        if let Ok(p) = input.parse::<LitBool>() {
67                            core = p.value;
68                        } else {
69                            return Err(parse::Error::new(
70                                ident.span(),
71                                "unexpected argument value; this should be a boolean",
72                            ));
73                        }
74                    }
75
76                    "peripherals" => {
77                        if let Ok(p) = input.parse::<LitBool>() {
78                            peripherals = p.value;
79                        } else {
80                            return Err(parse::Error::new(
81                                ident.span(),
82                                "unexpected argument value; this should be a boolean",
83                            ));
84                        }
85                    }
86
87                    "dispatchers" => {
88                        if let Ok(p) = input.parse::<ExprArray>() {
89                            for e in p.elems {
90                                match e {
91                                    Expr::Path(ep) => {
92                                        let path = ep.path;
93                                        let ident = if path.leading_colon.is_some()
94                                            || path.segments.len() != 1
95                                        {
96                                            return Err(parse::Error::new(
97                                                path.span(),
98                                                "interrupt must be an identifier, not a path",
99                                            ));
100                                        } else {
101                                            path.segments[0].ident.clone()
102                                        };
103                                        let span = ident.span();
104                                        if dispatchers.contains_key(&ident) {
105                                            return Err(parse::Error::new(
106                                                span,
107                                                "this extern interrupt is listed more than once",
108                                            ));
109                                        } else {
110                                            dispatchers
111                                                .insert(ident, Dispatcher { attrs: ep.attrs });
112                                        }
113                                    }
114                                    _ => {
115                                        return Err(parse::Error::new(
116                                            e.span(),
117                                            "interrupt must be an identifier",
118                                        ));
119                                    }
120                                }
121                            }
122                        } else {
123                            return Err(parse::Error::new(
124                                ident.span(),
125                                // increasing the length of the error message will break rustfmt
126                                "unexpected argument value; expected an array",
127                            ));
128                        }
129                    }
130
131                    "backend" => {
132                        if let Ok(p) = input.parse::<BackendArgs>() {
133                            backend = Some(p);
134                        } else {
135                            return Err(parse::Error::new(
136                                ident.span(),
137                                "unable to parse backend configuration",
138                            ));
139                        }
140                    }
141
142                    _ => {
143                        return Err(parse::Error::new(ident.span(), "unexpected argument"));
144                    }
145                }
146
147                if input.is_empty() {
148                    break;
149                }
150
151                // ,
152                let _: Token![,] = input.parse()?;
153            }
154
155            let device = if let Some(device) = device {
156                device
157            } else {
158                return Err(parse::Error::new(input.span(), "missing `device = ...`"));
159            };
160
161            Ok(AppArgs {
162                device,
163                core,
164                peripherals,
165                dispatchers,
166                backend,
167            })
168        })
169        .parse2(tokens)
170    }
171}
172
173impl App {
174    pub(crate) fn parse(args: AppArgs, input: Input) -> parse::Result<Self> {
175        let mut init = None;
176        let mut idle = None;
177
178        let mut shared_resources_ident = None;
179        let mut shared_resources_vis = Visibility::Inherited;
180        let mut shared_resources = Map::new();
181
182        let mut local_resources_ident = None;
183        let mut local_resources_vis = Visibility::Inherited;
184        let mut local_resources = Map::new();
185
186        let mut hardware_tasks = Map::new();
187        let mut software_tasks = Map::new();
188        let mut user_imports = vec![];
189        let mut user_code = vec![];
190
191        let mut seen_idents = HashSet::<Ident>::new();
192        let mut bindings = HashSet::<Ident>::new();
193
194        let mut check_binding = |ident: &Ident| {
195            if bindings.contains(ident) {
196                return Err(parse::Error::new(
197                    ident.span(),
198                    "this interrupt is already bound",
199                ));
200            } else {
201                bindings.insert(ident.clone());
202            }
203
204            Ok(())
205        };
206
207        let mut check_ident = |ident: &Ident| {
208            if seen_idents.contains(ident) {
209                return Err(parse::Error::new(
210                    ident.span(),
211                    "this identifier has already been used",
212                ));
213            } else {
214                seen_idents.insert(ident.clone());
215            }
216
217            Ok(())
218        };
219
220        for mut item in input.items {
221            match item {
222                Item::Fn(mut item) => {
223                    let span = item.sig.ident.span();
224                    if let Some(pos) = item
225                        .attrs
226                        .iter()
227                        .position(|attr| util::attr_eq(attr, "init"))
228                    {
229                        let args = InitArgs::parse(
230                            item.attrs.remove(pos).parse_args().unwrap_or_default(),
231                        )?;
232
233                        // If an init function already exists, error
234                        if init.is_some() {
235                            return Err(parse::Error::new(
236                                span,
237                                "`#[init]` function must appear at most once",
238                            ));
239                        }
240
241                        check_ident(&item.sig.ident)?;
242
243                        init = Some(Init::parse(args, item)?);
244                    } else if let Some(pos) = item
245                        .attrs
246                        .iter()
247                        .position(|attr| util::attr_eq(attr, "idle"))
248                    {
249                        let args = IdleArgs::parse(
250                            item.attrs.remove(pos).parse_args().unwrap_or_default(),
251                        )?;
252
253                        // If an idle function already exists, error
254                        if idle.is_some() {
255                            return Err(parse::Error::new(
256                                span,
257                                "`#[idle]` function must appear at most once",
258                            ));
259                        }
260
261                        check_ident(&item.sig.ident)?;
262
263                        idle = Some(Idle::parse(args, item)?);
264                    } else if let Some(pos) = item
265                        .attrs
266                        .iter()
267                        .position(|attr| util::attr_eq(attr, "task"))
268                    {
269                        if hardware_tasks.contains_key(&item.sig.ident)
270                            || software_tasks.contains_key(&item.sig.ident)
271                        {
272                            return Err(parse::Error::new(
273                                span,
274                                "this task is defined multiple times",
275                            ));
276                        }
277
278                        match syntax_parse::task_args(
279                            item.attrs.remove(pos).parse_args().unwrap_or_default(),
280                        )? {
281                            Either::Left(args) => {
282                                check_binding(&args.binds)?;
283                                check_ident(&item.sig.ident)?;
284
285                                hardware_tasks.insert(
286                                    item.sig.ident.clone(),
287                                    HardwareTask::parse(args, item)?,
288                                );
289                            }
290
291                            Either::Right(args) => {
292                                check_ident(&item.sig.ident)?;
293
294                                software_tasks.insert(
295                                    item.sig.ident.clone(),
296                                    SoftwareTask::parse(args, item)?,
297                                );
298                            }
299                        }
300                    } else {
301                        // Forward normal functions
302                        user_code.push(Item::Fn(item.clone()));
303                    }
304                }
305
306                Item::Struct(ref mut struct_item) => {
307                    // Match structures with the attribute #[shared], name of structure is not
308                    // important
309                    if let Some(_pos) = struct_item
310                        .attrs
311                        .iter()
312                        .position(|attr| util::attr_eq(attr, "shared"))
313                    {
314                        let span = struct_item.ident.span();
315
316                        shared_resources_ident = Some(struct_item.ident.clone());
317
318                        if !shared_resources.is_empty() {
319                            return Err(parse::Error::new(
320                                span,
321                                "`#[shared]` struct must appear at most once",
322                            ));
323                        }
324
325                        shared_resources_vis = struct_item.vis.clone();
326
327                        if let Fields::Named(fields) = &mut struct_item.fields {
328                            for field in &mut fields.named {
329                                let ident = field.ident.as_ref().expect("UNREACHABLE");
330
331                                if shared_resources.contains_key(ident) {
332                                    return Err(parse::Error::new(
333                                        ident.span(),
334                                        "this resource is listed more than once",
335                                    ));
336                                }
337
338                                shared_resources
339                                    .insert(ident.clone(), SharedResource::parse(field)?);
340                            }
341                        } else {
342                            return Err(parse::Error::new(
343                                struct_item.span(),
344                                "this `struct` must have named fields",
345                            ));
346                        }
347                    } else if let Some(_pos) = struct_item
348                        .attrs
349                        .iter()
350                        .position(|attr| util::attr_eq(attr, "local"))
351                    {
352                        let span = struct_item.ident.span();
353
354                        local_resources_ident = Some(struct_item.ident.clone());
355
356                        if !local_resources.is_empty() {
357                            return Err(parse::Error::new(
358                                span,
359                                "`#[local]` struct must appear at most once",
360                            ));
361                        }
362
363                        local_resources_vis = struct_item.vis.clone();
364
365                        if let Fields::Named(fields) = &mut struct_item.fields {
366                            for field in &mut fields.named {
367                                let ident = field.ident.as_ref().expect("UNREACHABLE");
368
369                                if local_resources.contains_key(ident) {
370                                    return Err(parse::Error::new(
371                                        ident.span(),
372                                        "this resource is listed more than once",
373                                    ));
374                                }
375
376                                local_resources.insert(ident.clone(), LocalResource::parse(field)?);
377                            }
378                        } else {
379                            return Err(parse::Error::new(
380                                struct_item.span(),
381                                "this `struct` must have named fields",
382                            ));
383                        }
384                    } else {
385                        // Structure without the #[resources] attribute should just be passed along
386                        user_code.push(item.clone());
387                    }
388                }
389
390                Item::ForeignMod(mod_) => {
391                    if !util::abi_is_rust(&mod_.abi) {
392                        return Err(parse::Error::new(
393                            mod_.abi.extern_token.span(),
394                            "this `extern` block must use the \"Rust\" ABI",
395                        ));
396                    }
397
398                    for item in mod_.items {
399                        if let ForeignItem::Fn(mut item) = item {
400                            let span = item.sig.ident.span();
401                            if let Some(pos) = item
402                                .attrs
403                                .iter()
404                                .position(|attr| util::attr_eq(attr, "init"))
405                            {
406                                let args = InitArgs::parse(
407                                    item.attrs.remove(pos).parse_args().unwrap_or_default(),
408                                )?;
409                                // If an init function already exists, error
410                                if init.is_some() {
411                                    return Err(parse::Error::new(
412                                        span,
413                                        "`#[init]` function must appear at most once",
414                                    ));
415                                }
416
417                                check_ident(&item.sig.ident)?;
418
419                                init = Some(Init::parse_foreign(args, item)?);
420                            } else if let Some(pos) = item
421                                .attrs
422                                .iter()
423                                .position(|attr| util::attr_eq(attr, "idle"))
424                            {
425                                let args = IdleArgs::parse(
426                                    item.attrs.remove(pos).parse_args().unwrap_or_default(),
427                                )?;
428
429                                // If an idle function already exists, error
430                                if idle.is_some() {
431                                    return Err(parse::Error::new(
432                                        span,
433                                        "`#[idle]` function must appear at most once",
434                                    ));
435                                }
436
437                                check_ident(&item.sig.ident)?;
438
439                                idle = Some(Idle::parse_foreign(args, item)?);
440                            } else if let Some(pos) = item
441                                .attrs
442                                .iter()
443                                .position(|attr| util::attr_eq(attr, "task"))
444                            {
445                                if hardware_tasks.contains_key(&item.sig.ident)
446                                    || software_tasks.contains_key(&item.sig.ident)
447                                {
448                                    return Err(parse::Error::new(
449                                        span,
450                                        "this task is defined multiple times",
451                                    ));
452                                }
453
454                                if item.attrs.len() != 1 {
455                                    return Err(parse::Error::new(
456                                        span,
457                                        "`extern` task required `#[task(..)]` attribute",
458                                    ));
459                                }
460
461                                match syntax_parse::task_args(
462                                    item.attrs.remove(pos).parse_args().unwrap_or_default(),
463                                )? {
464                                    Either::Left(args) => {
465                                        check_binding(&args.binds)?;
466                                        check_ident(&item.sig.ident)?;
467
468                                        hardware_tasks.insert(
469                                            item.sig.ident.clone(),
470                                            HardwareTask::parse_foreign(args, item)?,
471                                        );
472                                    }
473
474                                    Either::Right(args) => {
475                                        check_ident(&item.sig.ident)?;
476
477                                        software_tasks.insert(
478                                            item.sig.ident.clone(),
479                                            SoftwareTask::parse_foreign(args, item)?,
480                                        );
481                                    }
482                                }
483                            } else {
484                                return Err(parse::Error::new(
485                                    span,
486                                    "`extern` task, init or idle must have either `#[task(..)]`,
487                                    `#[init(..)]` or `#[idle(..)]` attribute",
488                                ));
489                            }
490                        } else {
491                            return Err(parse::Error::new(
492                                item.span(),
493                                "this item must live outside the `#[app]` module",
494                            ));
495                        }
496                    }
497                }
498                Item::Use(itemuse_) => {
499                    // Store the user provided use-statements
500                    user_imports.push(itemuse_.clone());
501                }
502                _ => {
503                    // Anything else within the module should not make any difference
504                    user_code.push(item.clone());
505                }
506            }
507        }
508
509        let shared_resources_ident =
510            shared_resources_ident.expect("No `#[shared]` resource struct defined");
511        let local_resources_ident =
512            local_resources_ident.expect("No `#[local]` resource struct defined");
513        let init = init.expect("No `#[init]` function defined");
514
515        if shared_resources_ident != init.user_shared_struct {
516            return Err(parse::Error::new(
517                init.user_shared_struct.span(),
518                format!(
519                    "This name and the one defined on `#[shared]` are not the same. Should this be `{shared_resources_ident}`?"
520                ),
521            ));
522        }
523
524        if local_resources_ident != init.user_local_struct {
525            return Err(parse::Error::new(
526                init.user_local_struct.span(),
527                format!(
528                    "This name and the one defined on `#[local]` are not the same. Should this be `{local_resources_ident}`?"
529                ),
530            ));
531        }
532
533        Ok(App {
534            args,
535            name: input.ident,
536            init,
537            idle,
538            shared_resources,
539            shared_resources_vis,
540            local_resources,
541            local_resources_vis,
542            user_imports,
543            user_code,
544            hardware_tasks,
545            software_tasks,
546        })
547    }
548}