diff options
Diffstat (limited to 'macros/src/syntax.rs')
-rw-r--r-- | macros/src/syntax.rs | 1235 |
1 files changed, 1235 insertions, 0 deletions
diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs new file mode 100644 index 00000000..24586dcf --- /dev/null +++ b/macros/src/syntax.rs @@ -0,0 +1,1235 @@ +use std::{ + collections::{HashMap, HashSet}, + iter, u8, +}; + +use proc_macro2::Span; +use syn::{ + braced, bracketed, parenthesized, + parse::{self, Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + token::Brace, + ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, + ItemForeignMod, ItemStatic, LitInt, Path, PathArguments, PathSegment, ReturnType, Stmt, Token, + Type, TypeTuple, Visibility, +}; + +pub struct AppArgs { + pub device: Path, +} + +impl Parse for AppArgs { + fn parse(input: ParseStream) -> parse::Result<Self> { + let mut device = None; + loop { + if input.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = input.parse()?; + let _eq_token: Token![=] = input.parse()?; + + let ident_s = ident.to_string(); + match &*ident_s { + "device" => { + if device.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + device = Some(input.parse()?); + } + _ => { + return Err(parse::Error::new( + ident.span(), + "expected `device`; other keys are not accepted", + )) + } + } + + if input.is_empty() { + break; + } + + // , + let _: Token![,] = input.parse()?; + } + + Ok(AppArgs { + device: device.ok_or(parse::Error::new( + Span::call_site(), + "`device` argument is required", + ))?, + }) + } +} + +pub struct Input { + _const_token: Token![const], + _ident: Ident, + _colon_token: Token![:], + _ty: TypeTuple, + _eq_token: Token![=], + _brace_token: Brace, + pub items: Vec<Item>, + _semi_token: Token![;], +} + +impl Parse for Input { + fn parse(input: ParseStream) -> parse::Result<Self> { + fn parse_items(input: ParseStream) -> parse::Result<Vec<Item>> { + let mut items = vec![]; + + while !input.is_empty() { + items.push(input.parse()?); + } + + Ok(items) + } + + let content; + Ok(Input { + _const_token: input.parse()?, + _ident: input.parse()?, + _colon_token: input.parse()?, + _ty: input.parse()?, + _eq_token: input.parse()?, + _brace_token: braced!(content in input), + items: content.call(parse_items)?, + _semi_token: input.parse()?, + }) + } +} + +pub struct App { + pub args: AppArgs, + pub idle: Option<Idle>, + pub init: Init, + pub exceptions: Exceptions, + pub interrupts: Interrupts, + pub resources: Resources, + pub tasks: Tasks, + pub free_interrupts: FreeInterrupts, +} + +impl App { + pub fn parse(items: Vec<Item>, args: AppArgs) -> parse::Result<Self> { + let mut idle = None; + let mut init = None; + let mut exceptions = HashMap::new(); + let mut interrupts = HashMap::new(); + let mut resources = HashMap::new(); + let mut tasks = HashMap::new(); + let mut free_interrupts = None; + + for item in items { + match item { + Item::Fn(mut item) => { + if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "idle")) { + if idle.is_some() { + return Err(parse::Error::new( + item.span(), + "`#[idle]` function must appear at most once", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + idle = Some(Idle::check(args, item)?); + } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "init")) { + if init.is_some() { + return Err(parse::Error::new( + item.span(), + "`#[init]` function must appear exactly once", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + init = Some(Init::check(args, item)?); + } else if let Some(pos) = + item.attrs.iter().position(|attr| eq(attr, "exception")) + { + if exceptions.contains_key(&item.ident) + || interrupts.contains_key(&item.ident) + || tasks.contains_key(&item.ident) + { + return Err(parse::Error::new( + item.ident.span(), + "this task is defined multiple times", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + exceptions.insert(item.ident.clone(), Exception::check(args, item)?); + } else if let Some(pos) = + item.attrs.iter().position(|attr| eq(attr, "interrupt")) + { + if exceptions.contains_key(&item.ident) + || interrupts.contains_key(&item.ident) + || tasks.contains_key(&item.ident) + { + return Err(parse::Error::new( + item.ident.span(), + "this task is defined multiple times", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + interrupts.insert(item.ident.clone(), Interrupt::check(args, item)?); + } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "task")) { + if exceptions.contains_key(&item.ident) + || interrupts.contains_key(&item.ident) + || tasks.contains_key(&item.ident) + { + return Err(parse::Error::new( + item.ident.span(), + "this task is defined multiple times", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + tasks.insert(item.ident.clone(), Task::check(args, item)?); + } else { + return Err(parse::Error::new( + item.span(), + "this item must live outside the `#[app]` module", + )); + } + } + Item::Static(item) => { + if resources.contains_key(&item.ident) { + return Err(parse::Error::new( + item.ident.span(), + "this resource is listed twice", + )); + } + + resources.insert(item.ident.clone(), Resource::check(item)?); + } + Item::ForeignMod(item) => { + if free_interrupts.is_some() { + return Err(parse::Error::new( + item.abi.extern_token.span(), + "`extern` block can only appear at most once", + )); + } + + free_interrupts = Some(FreeInterrupt::parse(item)?); + } + _ => { + return Err(parse::Error::new( + item.span(), + "this item must live outside the `#[app]` module", + )) + } + } + } + + Ok(App { + args, + idle, + init: init.ok_or_else(|| { + parse::Error::new(Span::call_site(), "`#[init]` function is missing") + })?, + exceptions, + interrupts, + resources, + tasks, + free_interrupts: free_interrupts.unwrap_or_else(|| FreeInterrupts::new()), + }) + } + + /// Returns an iterator over all resource accesses. + /// + /// Each resource access include the priority it's accessed at (`u8`) and the name of the + /// resource (`Ident`). A resource may appear more than once in this iterator + pub fn resource_accesses(&self) -> impl Iterator<Item = (u8, &Ident)> { + self.idle + .as_ref() + .map(|idle| -> Box<Iterator<Item = _>> { + Box::new(idle.args.resources.iter().map(|res| (0, res))) + }) + .unwrap_or_else(|| Box::new(iter::empty())) + .chain(self.exceptions.values().flat_map(|e| { + e.args + .resources + .iter() + .map(move |res| (e.args.priority, res)) + })) + .chain(self.interrupts.values().flat_map(|i| { + i.args + .resources + .iter() + .map(move |res| (i.args.priority, res)) + })) + .chain(self.tasks.values().flat_map(|t| { + t.args + .resources + .iter() + .map(move |res| (t.args.priority, res)) + })) + } + + /// Returns an iterator over all `spawn` calls + /// + /// Each spawn call includes the priority of the task from which it's issued and the name of the + /// task that's spawned. A task may appear more that once in this iterator. + /// + /// A priority of `None` means that this being called from `init` + pub fn spawn_calls(&self) -> impl Iterator<Item = (Option<u8>, &Ident)> { + self.init + .args + .spawn + .iter() + .map(|s| (None, s)) + .chain( + self.idle + .as_ref() + .map(|idle| -> Box<Iterator<Item = _>> { + Box::new(idle.args.spawn.iter().map(|s| (Some(0), s))) + }) + .unwrap_or_else(|| Box::new(iter::empty())), + ) + .chain( + self.exceptions + .values() + .flat_map(|e| e.args.spawn.iter().map(move |s| (Some(e.args.priority), s))), + ) + .chain( + self.interrupts + .values() + .flat_map(|i| i.args.spawn.iter().map(move |s| (Some(i.args.priority), s))), + ) + .chain( + self.tasks + .values() + .flat_map(|t| t.args.spawn.iter().map(move |s| (Some(t.args.priority), s))), + ) + } + + /// Returns an iterator over all `schedule` calls + /// + /// Each spawn call includes the priority of the task from which it's issued and the name of the + /// task that's spawned. A task may appear more that once in this iterator. + #[allow(dead_code)] + pub fn schedule_calls(&self) -> impl Iterator<Item = (Option<u8>, &Ident)> { + self.init + .args + .schedule + .iter() + .map(|s| (None, s)) + .chain( + self.idle + .as_ref() + .map(|idle| -> Box<Iterator<Item = _>> { + Box::new(idle.args.schedule.iter().map(|s| (Some(0), s))) + }) + .unwrap_or_else(|| Box::new(iter::empty())), + ) + .chain(self.exceptions.values().flat_map(|e| { + e.args + .schedule + .iter() + .map(move |s| (Some(e.args.priority), s)) + })) + .chain(self.interrupts.values().flat_map(|i| { + i.args + .schedule + .iter() + .map(move |s| (Some(i.args.priority), s)) + })) + .chain(self.tasks.values().flat_map(|t| { + t.args + .schedule + .iter() + .map(move |s| (Some(t.args.priority), s)) + })) + } + + #[allow(dead_code)] + pub fn schedule_callers(&self) -> impl Iterator<Item = (Ident, &Idents)> { + self.idle + .as_ref() + .map(|idle| -> Box<Iterator<Item = _>> { + Box::new(iter::once(( + Ident::new("idle", Span::call_site()), + &idle.args.schedule, + ))) + }) + .unwrap_or_else(|| Box::new(iter::empty())) + .chain(iter::once(( + Ident::new("init", Span::call_site()), + &self.init.args.schedule, + ))) + .chain( + self.exceptions + .iter() + .map(|(name, exception)| (name.clone(), &exception.args.schedule)), + ) + .chain( + self.interrupts + .iter() + .map(|(name, interrupt)| (name.clone(), &interrupt.args.schedule)), + ) + .chain( + self.tasks + .iter() + .map(|(name, task)| (name.clone(), &task.args.schedule)), + ) + } + + pub fn spawn_callers(&self) -> impl Iterator<Item = (Ident, &Idents)> { + self.idle + .as_ref() + .map(|idle| -> Box<Iterator<Item = _>> { + Box::new(iter::once(( + Ident::new("idle", Span::call_site()), + &idle.args.spawn, + ))) + }) + .unwrap_or_else(|| Box::new(iter::empty())) + .chain(iter::once(( + Ident::new("init", Span::call_site()), + &self.init.args.spawn, + ))) + .chain( + self.exceptions + .iter() + .map(|(name, exception)| (name.clone(), &exception.args.spawn)), + ) + .chain( + self.interrupts + .iter() + .map(|(name, interrupt)| (name.clone(), &interrupt.args.spawn)), + ) + .chain( + self.tasks + .iter() + .map(|(name, task)| (name.clone(), &task.args.spawn)), + ) + } +} + +pub type Idents = HashSet<Ident>; + +pub type Exceptions = HashMap<Ident, Exception>; + +pub type Interrupts = HashMap<Ident, Interrupt>; + +pub type Resources = HashMap<Ident, Resource>; + +pub type Statics = Vec<ItemStatic>; + +pub type Tasks = HashMap<Ident, Task>; + +pub type FreeInterrupts = HashMap<Ident, FreeInterrupt>; + +pub struct Idle { + pub args: IdleArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub statics: HashMap<Ident, Static>, + pub stmts: Vec<Stmt>, +} + +pub type IdleArgs = InitArgs; + +impl Idle { + fn check(args: IdleArgs, item: ItemFn) -> parse::Result<Self> { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.inputs.is_empty() + && item.decl.variadic.is_none() + && is_bottom(&item.decl.output); + + let span = item.span(); + + if !valid_signature { + return Err(parse::Error::new( + span, + "`idle` must have type signature `[unsafe] fn() -> !`", + )); + } + + let (statics, stmts) = extract_statics(item.block.stmts); + + Ok(Idle { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics: Static::parse(statics)?, + stmts, + }) + } +} + +pub struct InitArgs { + pub resources: Idents, + pub schedule: Idents, + pub spawn: Idents, +} + +impl Default for InitArgs { + fn default() -> Self { + InitArgs { + resources: Idents::new(), + schedule: Idents::new(), + spawn: Idents::new(), + } + } +} + +impl Parse for InitArgs { + fn parse(input: ParseStream) -> parse::Result<InitArgs> { + if input.is_empty() { + return Ok(InitArgs::default()); + } + + let mut resources = None; + let mut schedule = None; + let mut spawn = None; + + let content; + parenthesized!(content in input); + loop { + if content.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = content.parse()?; + let _: Token![=] = content.parse()?; + + let ident_s = ident.to_string(); + match &*ident_s { + "schedule" if cfg!(not(feature = "timer-queue")) => { + return Err(parse::Error::new( + ident.span(), + "The `schedule` API requires that the `timer-queue` feature is \ + enabled in the `cortex-m-rtfm` crate", + )); + } + "resources" | "schedule" | "spawn" => {} // OK + _ => { + return Err(parse::Error::new( + ident.span(), + "expected one of: resources, schedule or spawn", + )) + } + } + + // .. [#(#idents)*] + let inner; + bracketed!(inner in content); + let mut idents = Idents::new(); + for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { + if idents.contains(&ident) { + return Err(parse::Error::new( + ident.span(), + "element appears more than once in list", + )); + } + + idents.insert(ident); + } + + let ident_s = ident.to_string(); + match &*ident_s { + "resources" => { + if resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + resources = Some(idents); + } + "schedule" => { + if schedule.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + schedule = Some(idents); + } + "spawn" => { + if spawn.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + spawn = Some(idents); + } + _ => unreachable!(), + } + + if content.is_empty() { + break; + } + + // , + let _: Token![,] = content.parse()?; + } + + Ok(InitArgs { + resources: resources.unwrap_or(Idents::new()), + schedule: schedule.unwrap_or(Idents::new()), + spawn: spawn.unwrap_or(Idents::new()), + }) + } +} + +pub struct Assign { + pub left: Ident, + pub right: Box<Expr>, +} + +pub struct Init { + pub args: InitArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub statics: HashMap<Ident, Static>, + pub stmts: Vec<Stmt>, + pub assigns: Vec<Assign>, +} + +impl Init { + fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.inputs.is_empty() + && item.decl.variadic.is_none() + && is_unit(&item.decl.output); + + let span = item.span(); + + if !valid_signature { + return Err(parse::Error::new( + span, + "`init` must have type signature `[unsafe] fn()`", + )); + } + + let (statics, stmts) = extract_statics(item.block.stmts); + let (stmts, assigns) = extract_assignments(stmts); + + Ok(Init { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics: Static::parse(statics)?, + stmts, + assigns, + }) + } +} + +pub struct Exception { + pub args: ExceptionArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub statics: Statics, + pub stmts: Vec<Stmt>, +} + +pub struct ExceptionArgs { + pub priority: u8, + pub resources: Idents, + pub schedule: Idents, + pub spawn: Idents, +} + +impl Parse for ExceptionArgs { + fn parse(input: ParseStream) -> parse::Result<Self> { + parse_args(input, false).map( + |TaskArgs { + priority, + resources, + schedule, + spawn, + .. + }| { + ExceptionArgs { + priority, + resources, + schedule, + spawn, + } + }, + ) + } +} + +impl Exception { + fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result<Self> { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.inputs.is_empty() + && item.decl.variadic.is_none() + && is_unit(&item.decl.output); + + if !valid_signature { + return Err(parse::Error::new( + item.span(), + "`exception` handlers must have type signature `[unsafe] fn()`", + )); + } + + let span = item.ident.span(); + match &*item.ident.to_string() { + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" => {} // OK + "SysTick" => { + if cfg!(feature = "timer-queue") { + return Err(parse::Error::new( + span, + "the `SysTick` exception can't be used because it's used by \ + the runtime when the `timer-queue` feature is enabled", + )); + } + } + _ => { + return Err(parse::Error::new( + span, + "only exceptions with configurable priority can be used as hardware tasks", + )); + } + } + + let (statics, stmts) = extract_statics(item.block.stmts); + + Ok(Exception { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics, + stmts, + }) + } +} + +pub struct Interrupt { + pub args: InterruptArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub statics: Statics, + pub stmts: Vec<Stmt>, +} + +pub type InterruptArgs = ExceptionArgs; + +impl Interrupt { + fn check(args: InterruptArgs, item: ItemFn) -> parse::Result<Self> { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.inputs.is_empty() + && item.decl.variadic.is_none() + && is_unit(&item.decl.output); + + let span = item.span(); + + if !valid_signature { + return Err(parse::Error::new( + span, + "`interrupt` handlers must have type signature `[unsafe] fn()`", + )); + } + + match &*item.ident.to_string() { + "init" | "idle" | "resources" => { + return Err(parse::Error::new( + span, + "`interrupt` handlers can NOT be named `idle`, `init` or `resources`", + )); + } + _ => {} + } + + let (statics, stmts) = extract_statics(item.block.stmts); + + Ok(Interrupt { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics, + stmts, + }) + } +} + +pub struct Resource { + pub singleton: bool, + pub attrs: Vec<Attribute>, + pub mutability: Option<Token![mut]>, + pub ty: Box<Type>, + pub expr: Option<Box<Expr>>, +} + +impl Resource { + fn check(mut item: ItemStatic) -> parse::Result<Resource> { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + item.span(), + "resources must have inherited / private visibility", + )); + } + + let uninitialized = match *item.expr { + Expr::Tuple(ref tuple) => tuple.elems.is_empty(), + _ => false, + }; + + let pos = item.attrs.iter().position(|attr| eq(attr, "Singleton")); + + if let Some(pos) = pos { + item.attrs[pos].path.segments.insert( + 0, + PathSegment::from(Ident::new("owned_singleton", Span::call_site())), + ); + } + + Ok(Resource { + singleton: pos.is_some(), + attrs: item.attrs, + mutability: item.mutability, + ty: item.ty, + expr: if uninitialized { None } else { Some(item.expr) }, + }) + } +} + +pub struct TaskArgs { + pub capacity: Option<u8>, + pub priority: u8, + pub resources: Idents, + pub spawn: Idents, + pub schedule: Idents, +} + +impl Default for TaskArgs { + fn default() -> Self { + TaskArgs { + capacity: None, + priority: 1, + resources: Idents::new(), + schedule: Idents::new(), + spawn: Idents::new(), + } + } +} + +impl Parse for TaskArgs { + fn parse(input: ParseStream) -> parse::Result<Self> { + parse_args(input, true) + } +} + +// Parser shared by TaskArgs and ExceptionArgs / InterruptArgs +fn parse_args(input: ParseStream, accept_capacity: bool) -> parse::Result<TaskArgs> { + if input.is_empty() { + return Ok(TaskArgs::default()); + } + + let mut capacity = None; + let mut priority = None; + let mut resources = None; + let mut schedule = None; + let mut spawn = None; + + let content; + parenthesized!(content in input); + loop { + if content.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = content.parse()?; + let _: Token![=] = content.parse()?; + + let ident_s = ident.to_string(); + match &*ident_s { + "capacity" if accept_capacity => { + // #lit + let lit: LitInt = content.parse()?; + + if lit.suffix() != IntSuffix::None { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.value(); + if value > u64::from(u8::MAX) || value == 0 { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 1...255", + )); + } + + capacity = Some(value as u8); + } + "priority" => { + // #lit + let lit: LitInt = content.parse()?; + + if lit.suffix() != IntSuffix::None { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.value(); + if value > u64::from(u8::MAX) { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 0...255", + )); + } + + priority = Some(value as u8); + } + "schedule" if cfg!(not(feature = "timer-queue")) => { + return Err(parse::Error::new( + ident.span(), + "The `schedule` API requires that the `timer-queue` feature is \ + enabled in the `cortex-m-rtfm` crate", + )); + } + "resources" | "schedule" | "spawn" => { + // .. [#(#idents)*] + let inner; + bracketed!(inner in content); + let mut idents = Idents::new(); + for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { + if idents.contains(&ident) { + return Err(parse::Error::new( + ident.span(), + "element appears more than once in list", + )); + } + + idents.insert(ident); + } + + match &*ident_s { + "resources" => { + if resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + resources = Some(idents); + } + "schedule" => { + if schedule.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + schedule = Some(idents); + } + "spawn" => { + if spawn.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + spawn = Some(idents); + } + _ => unreachable!(), + } + } + _ => { + return Err(parse::Error::new( + ident.span(), + "expected one of: priority, resources, schedule or spawn", + )) + } + } + + if content.is_empty() { + break; + } + + // , + let _: Token![,] = content.parse()?; + } + + Ok(TaskArgs { + capacity, + priority: priority.unwrap_or(1), + resources: resources.unwrap_or(Idents::new()), + schedule: schedule.unwrap_or(Idents::new()), + spawn: spawn.unwrap_or(Idents::new()), + }) +} + +pub struct Static { + pub attrs: Vec<Attribute>, + pub ty: Box<Type>, + pub expr: Box<Expr>, +} + +impl Static { + fn parse(items: Vec<ItemStatic>) -> parse::Result<HashMap<Ident, Static>> { + let mut statics = HashMap::new(); + + for item in items { + if statics.contains_key(&item.ident) { + return Err(parse::Error::new( + item.ident.span(), + "this `static` is listed twice", + )); + } + + statics.insert( + item.ident, + Static { + attrs: item.attrs, + ty: item.ty, + expr: item.expr, + }, + ); + } + + Ok(statics) + } +} + +pub struct Task { + pub args: TaskArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub inputs: Vec<ArgCaptured>, + pub statics: HashMap<Ident, Static>, + pub stmts: Vec<Stmt>, +} + +impl Task { + fn check(args: TaskArgs, item: ItemFn) -> parse::Result<Self> { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.variadic.is_none() + && is_unit(&item.decl.output); + + let span = item.span(); + + if !valid_signature { + return Err(parse::Error::new( + span, + "`task` handlers must have type signature `[unsafe] fn(..)`", + )); + } + + let (statics, stmts) = extract_statics(item.block.stmts); + + let mut inputs = vec![]; + for input in item.decl.inputs { + if let FnArg::Captured(capture) = input { + inputs.push(capture); + } else { + return Err(parse::Error::new( + span, + "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", + )); + } + } + + match &*item.ident.to_string() { + "init" | "idle" | "resources" => { + return Err(parse::Error::new( + span, + "`task` handlers can NOT be named `idle`, `init` or `resources`", + )); + } + _ => {} + } + + Ok(Task { + args, + attrs: item.attrs, + unsafety: item.unsafety, + inputs, + statics: Static::parse(statics)?, + stmts, + }) + } +} + +pub struct FreeInterrupt { + pub attrs: Vec<Attribute>, +} + +impl FreeInterrupt { + fn parse(mod_: ItemForeignMod) -> parse::Result<FreeInterrupts> { + let mut free_interrupts = FreeInterrupts::new(); + + for item in mod_.items { + if let ForeignItem::Fn(f) = item { + let valid_signature = f.vis == Visibility::Inherited + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.inputs.is_empty() + && f.decl.variadic.is_none() + && is_unit(&f.decl.output); + + if !valid_signature { + return Err(parse::Error::new( + f.span(), + "free interrupts must have type signature `fn()`", + )); + } + + if free_interrupts.contains_key(&f.ident) { + return Err(parse::Error::new( + f.ident.span(), + "this interrupt appears twice", + )); + } + + free_interrupts.insert(f.ident, FreeInterrupt { attrs: f.attrs }); + } else { + return Err(parse::Error::new( + mod_.abi.extern_token.span(), + "`extern` block should only contains functions", + )); + } + } + + Ok(free_interrupts) + } +} + +fn eq(attr: &Attribute, name: &str) -> bool { + attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { + let pair = attr.path.segments.first().unwrap(); + let segment = pair.value(); + segment.arguments == PathArguments::None && segment.ident.to_string() == name + } +} + +/// Extracts `static mut` vars from the beginning of the given statements +fn extract_statics(stmts: Vec<Stmt>) -> (Statics, Vec<Stmt>) { + let mut istmts = stmts.into_iter(); + + let mut statics = Statics::new(); + let mut stmts = vec![]; + while let Some(stmt) = istmts.next() { + match stmt { + Stmt::Item(Item::Static(var)) => { + if var.mutability.is_some() { + statics.push(var); + } else { + stmts.push(Stmt::Item(Item::Static(var))); + break; + } + } + _ => { + stmts.push(stmt); + break; + } + } + } + + stmts.extend(istmts); + + (statics, stmts) +} + +fn extract_assignments(stmts: Vec<Stmt>) -> (Vec<Stmt>, Vec<Assign>) { + let mut istmts = stmts.into_iter().rev(); + + let mut assigns = vec![]; + let mut stmts = vec![]; + while let Some(stmt) = istmts.next() { + match stmt { + Stmt::Semi(Expr::Assign(assign), semi) => { + if let Expr::Path(ref expr) = *assign.left { + if expr.path.segments.len() == 1 { + assigns.push(Assign { + left: expr.path.segments[0].ident.clone(), + right: assign.right, + }); + continue; + } + } + + stmts.push(Stmt::Semi(Expr::Assign(assign), semi)); + } + _ => { + stmts.push(stmt); + break; + } + } + } + + stmts.extend(istmts); + + (stmts.into_iter().rev().collect(), assigns) +} + +fn is_bottom(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + if let Type::Never(_) = **ty { + true + } else { + false + } + } else { + false + } +} + +fn is_unit(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + if let Type::Tuple(ref tuple) = **ty { + tuple.elems.is_empty() + } else { + false + } + } else { + true + } +} |