diff options
Diffstat (limited to 'macros/src/syntax/parse/app.rs')
-rw-r--r-- | macros/src/syntax/parse/app.rs | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs new file mode 100644 index 00000000..7eb415d3 --- /dev/null +++ b/macros/src/syntax/parse/app.rs @@ -0,0 +1,539 @@ +use std::collections::HashSet; + +// use indexmap::map::Entry; +use proc_macro2::TokenStream as TokenStream2; +use syn::{ + parse::{self, ParseStream, Parser}, + spanned::Spanned, + Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Type, Visibility, +}; + +use super::Input; +use crate::syntax::{ + ast::{ + App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs, + LocalResource, Monotonic, MonotonicArgs, SharedResource, SoftwareTask, + }, + parse::{self as syntax_parse, util}, + Either, Map, Set, Settings, +}; + +impl AppArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> { + (|input: ParseStream<'_>| -> parse::Result<Self> { + let mut custom = Set::new(); + let mut device = None; + let mut peripherals = true; + let mut dispatchers = Dispatchers::new(); + + loop { + if input.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = input.parse()?; + let _eq_token: Token![=] = input.parse()?; + + if custom.contains(&ident) { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + custom.insert(ident.clone()); + + let ks = ident.to_string(); + + match &*ks { + "device" => { + if let Ok(p) = input.parse::<Path>() { + device = Some(p); + } else { + return Err(parse::Error::new( + ident.span(), + "unexpected argument value; this should be a path", + )); + } + } + + "peripherals" => { + if let Ok(p) = input.parse::<LitBool>() { + peripherals = p.value; + } else { + return Err(parse::Error::new( + ident.span(), + "unexpected argument value; this should be a boolean", + )); + } + } + + "dispatchers" => { + if let Ok(p) = input.parse::<ExprArray>() { + for e in p.elems { + match e { + Expr::Path(ep) => { + let path = ep.path; + let ident = if path.leading_colon.is_some() + || path.segments.len() != 1 + { + return Err(parse::Error::new( + path.span(), + "interrupt must be an identifier, not a path", + )); + } else { + path.segments[0].ident.clone() + }; + let span = ident.span(); + if dispatchers.contains_key(&ident) { + return Err(parse::Error::new( + span, + "this extern interrupt is listed more than once", + )); + } else { + dispatchers + .insert(ident, Dispatcher { attrs: ep.attrs }); + } + } + _ => { + return Err(parse::Error::new( + e.span(), + "interrupt must be an identifier", + )); + } + } + } + } else { + return Err(parse::Error::new( + ident.span(), + // increasing the length of the error message will break rustfmt + "unexpected argument value; expected an array", + )); + } + } + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if input.is_empty() { + break; + } + + // , + let _: Token![,] = input.parse()?; + } + + let device = if let Some(device) = device { + device + } else { + return Err(parse::Error::new(input.span(), "missing `device = ...`")); + }; + + Ok(AppArgs { + device, + peripherals, + dispatchers, + }) + }) + .parse2(tokens) + } +} + +impl App { + pub(crate) fn parse(args: AppArgs, input: Input, settings: &Settings) -> parse::Result<Self> { + let mut init = None; + let mut idle = None; + + let mut shared_resources_ident = None; + let mut shared_resources = Map::new(); + let mut local_resources_ident = None; + let mut local_resources = Map::new(); + let mut monotonics = Map::new(); + let mut hardware_tasks = Map::new(); + let mut software_tasks = Map::new(); + let mut user_imports = vec![]; + let mut user_code = vec![]; + + let mut seen_idents = HashSet::<Ident>::new(); + let mut bindings = HashSet::<Ident>::new(); + let mut monotonic_types = HashSet::<Type>::new(); + + let mut check_binding = |ident: &Ident| { + if bindings.contains(ident) { + return Err(parse::Error::new( + ident.span(), + "this interrupt is already bound", + )); + } else { + bindings.insert(ident.clone()); + } + + Ok(()) + }; + + let mut check_ident = |ident: &Ident| { + if seen_idents.contains(ident) { + return Err(parse::Error::new( + ident.span(), + "this identifier has already been used", + )); + } else { + seen_idents.insert(ident.clone()); + } + + Ok(()) + }; + + let mut check_monotonic = |ty: &Type| { + if monotonic_types.contains(ty) { + return Err(parse::Error::new( + ty.span(), + "this type is already used by another monotonic", + )); + } else { + monotonic_types.insert(ty.clone()); + } + + Ok(()) + }; + + for mut item in input.items { + match item { + Item::Fn(mut item) => { + let span = item.sig.ident.span(); + if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "init")) + { + let args = InitArgs::parse(item.attrs.remove(pos).tokens)?; + + // If an init function already exists, error + if init.is_some() { + return Err(parse::Error::new( + span, + "`#[init]` function must appear at most once", + )); + } + + check_ident(&item.sig.ident)?; + + init = Some(Init::parse(args, item)?); + } else if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "idle")) + { + let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?; + + // If an idle function already exists, error + if idle.is_some() { + return Err(parse::Error::new( + span, + "`#[idle]` function must appear at most once", + )); + } + + check_ident(&item.sig.ident)?; + + idle = Some(Idle::parse(args, item)?); + } else if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "task")) + { + if hardware_tasks.contains_key(&item.sig.ident) + || software_tasks.contains_key(&item.sig.ident) + { + return Err(parse::Error::new( + span, + "this task is defined multiple times", + )); + } + + match syntax_parse::task_args(item.attrs.remove(pos).tokens, settings)? { + Either::Left(args) => { + check_binding(&args.binds)?; + check_ident(&item.sig.ident)?; + + hardware_tasks.insert( + item.sig.ident.clone(), + HardwareTask::parse(args, item)?, + ); + } + + Either::Right(args) => { + check_ident(&item.sig.ident)?; + + software_tasks.insert( + item.sig.ident.clone(), + SoftwareTask::parse(args, item)?, + ); + } + } + } else { + // Forward normal functions + user_code.push(Item::Fn(item.clone())); + } + } + + Item::Struct(ref mut struct_item) => { + // Match structures with the attribute #[shared], name of structure is not + // important + if let Some(_pos) = struct_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "shared")) + { + let span = struct_item.ident.span(); + + shared_resources_ident = Some(struct_item.ident.clone()); + + if !shared_resources.is_empty() { + return Err(parse::Error::new( + span, + "`#[shared]` struct must appear at most once", + )); + } + + if struct_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + struct_item.span(), + "this item must have inherited / private visibility", + )); + } + + if let Fields::Named(fields) = &mut struct_item.fields { + for field in &mut fields.named { + let ident = field.ident.as_ref().expect("UNREACHABLE"); + + if shared_resources.contains_key(ident) { + return Err(parse::Error::new( + ident.span(), + "this resource is listed more than once", + )); + } + + shared_resources.insert( + ident.clone(), + SharedResource::parse(field, ident.span())?, + ); + } + } else { + return Err(parse::Error::new( + struct_item.span(), + "this `struct` must have named fields", + )); + } + } else if let Some(_pos) = struct_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "local")) + { + let span = struct_item.ident.span(); + + local_resources_ident = Some(struct_item.ident.clone()); + + if !local_resources.is_empty() { + return Err(parse::Error::new( + span, + "`#[local]` struct must appear at most once", + )); + } + + if struct_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + struct_item.span(), + "this item must have inherited / private visibility", + )); + } + + if let Fields::Named(fields) = &mut struct_item.fields { + for field in &mut fields.named { + let ident = field.ident.as_ref().expect("UNREACHABLE"); + + if local_resources.contains_key(ident) { + return Err(parse::Error::new( + ident.span(), + "this resource is listed more than once", + )); + } + + local_resources.insert( + ident.clone(), + LocalResource::parse(field, ident.span())?, + ); + } + } else { + return Err(parse::Error::new( + struct_item.span(), + "this `struct` must have named fields", + )); + } + } else { + // Structure without the #[resources] attribute should just be passed along + user_code.push(item.clone()); + } + } + + Item::ForeignMod(mod_) => { + if !util::abi_is_rust(&mod_.abi) { + return Err(parse::Error::new( + mod_.abi.extern_token.span(), + "this `extern` block must use the \"Rust\" ABI", + )); + } + + for item in mod_.items { + if let ForeignItem::Fn(mut item) = item { + let span = item.sig.ident.span(); + if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "task")) + { + if hardware_tasks.contains_key(&item.sig.ident) + || software_tasks.contains_key(&item.sig.ident) + { + return Err(parse::Error::new( + span, + "this task is defined multiple times", + )); + } + + if item.attrs.len() != 1 { + return Err(parse::Error::new( + span, + "`extern` task required `#[task(..)]` attribute", + )); + } + + match syntax_parse::task_args( + item.attrs.remove(pos).tokens, + settings, + )? { + Either::Left(args) => { + check_binding(&args.binds)?; + check_ident(&item.sig.ident)?; + + hardware_tasks.insert( + item.sig.ident.clone(), + HardwareTask::parse_foreign(args, item)?, + ); + } + + Either::Right(args) => { + check_ident(&item.sig.ident)?; + + software_tasks.insert( + item.sig.ident.clone(), + SoftwareTask::parse_foreign(args, item)?, + ); + } + } + } else { + return Err(parse::Error::new( + span, + "`extern` task required `#[task(..)]` attribute", + )); + } + } else { + return Err(parse::Error::new( + item.span(), + "this item must live outside the `#[app]` module", + )); + } + } + } + Item::Use(itemuse_) => { + // Store the user provided use-statements + user_imports.push(itemuse_.clone()); + } + Item::Type(ref mut type_item) => { + // Match types with the attribute #[monotonic] + if let Some(pos) = type_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "monotonic")) + { + let span = type_item.ident.span(); + + if monotonics.contains_key(&type_item.ident) { + return Err(parse::Error::new( + span, + "`#[monotonic(...)]` on a specific type must appear at most once", + )); + } + + if type_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + type_item.span(), + "this item must have inherited / private visibility", + )); + } + + check_monotonic(&*type_item.ty)?; + + let m = type_item.attrs.remove(pos); + let args = MonotonicArgs::parse(m)?; + + check_binding(&args.binds)?; + + let monotonic = Monotonic::parse(args, type_item, span)?; + + monotonics.insert(type_item.ident.clone(), monotonic); + } + + // All types are passed on + user_code.push(item.clone()); + } + _ => { + // Anything else within the module should not make any difference + user_code.push(item.clone()); + } + } + } + + let shared_resources_ident = + shared_resources_ident.expect("No `#[shared]` resource struct defined"); + let local_resources_ident = + local_resources_ident.expect("No `#[local]` resource struct defined"); + let init = init.expect("No `#[init]` function defined"); + + if shared_resources_ident != init.user_shared_struct { + return Err(parse::Error::new( + init.user_shared_struct.span(), + format!( + "This name and the one defined on `#[shared]` are not the same. Should this be `{}`?", + shared_resources_ident + ), + )); + } + + if local_resources_ident != init.user_local_struct { + return Err(parse::Error::new( + init.user_local_struct.span(), + format!( + "This name and the one defined on `#[local]` are not the same. Should this be `{}`?", + local_resources_ident + ), + )); + } + + Ok(App { + args, + name: input.ident, + init, + idle, + monotonics, + shared_resources, + local_resources, + user_imports, + user_code, + hardware_tasks, + software_tasks, + }) + } +} |