aboutsummaryrefslogtreecommitdiff
path: root/macros/src/syntax/parse
diff options
context:
space:
mode:
authorGravatar Emil Fresk <emil.fresk@gmail.com> 2022-12-31 14:45:13 +0100
committerGravatar Henrik Tjäder <henrik@tjaders.com> 2023-03-01 00:29:10 +0100
commit7614b96fe45240dafe91ae549e712b560e2d4c10 (patch)
tree30e1666c0aac09480e1a87e5fe7965487f4a99e6 /macros/src/syntax/parse
parent1c5db277e4161470136dbd2a11e914ff1d383581 (diff)
downloadrtic-7614b96fe45240dafe91ae549e712b560e2d4c10.tar.gz
rtic-7614b96fe45240dafe91ae549e712b560e2d4c10.tar.zst
rtic-7614b96fe45240dafe91ae549e712b560e2d4c10.zip
RTIC v2: Initial commit
rtic-syntax is now part of RTIC repository
Diffstat (limited to 'macros/src/syntax/parse')
-rw-r--r--macros/src/syntax/parse/app.rs539
-rw-r--r--macros/src/syntax/parse/hardware_task.rs96
-rw-r--r--macros/src/syntax/parse/idle.rs45
-rw-r--r--macros/src/syntax/parse/init.rs52
-rw-r--r--macros/src/syntax/parse/monotonic.rs42
-rw-r--r--macros/src/syntax/parse/resource.rs55
-rw-r--r--macros/src/syntax/parse/software_task.rs86
-rw-r--r--macros/src/syntax/parse/util.rs338
8 files changed, 1253 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,
+ })
+ }
+}
diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs
new file mode 100644
index 00000000..304bfcd3
--- /dev/null
+++ b/macros/src/syntax/parse/hardware_task.rs
@@ -0,0 +1,96 @@
+use syn::{parse, ForeignItemFn, ItemFn, Stmt};
+
+use crate::syntax::parse::util::FilterAttrs;
+use crate::syntax::{
+ ast::{HardwareTask, HardwareTaskArgs},
+ parse::util,
+};
+
+impl HardwareTask {
+ pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result<Self> {
+ let span = item.sig.ident.span();
+ let valid_signature = util::check_fn_signature(&item, false)
+ && item.sig.inputs.len() == 1
+ && util::type_is_unit(&item.sig.output);
+
+ let name = item.sig.ident.to_string();
+
+ if name == "init" || name == "idle" {
+ return Err(parse::Error::new(
+ span,
+ "tasks cannot be named `init` or `idle`",
+ ));
+ }
+
+ if valid_signature {
+ if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
+ if rest.is_empty() {
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
+
+ return Ok(HardwareTask {
+ args,
+ cfgs,
+ attrs,
+ context,
+ stmts: item.block.stmts,
+ is_extern: false,
+ });
+ }
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "this task handler must have type signature `fn({}::Context)`",
+ name
+ ),
+ ))
+ }
+}
+
+impl HardwareTask {
+ pub(crate) fn parse_foreign(
+ args: HardwareTaskArgs,
+ item: ForeignItemFn,
+ ) -> parse::Result<Self> {
+ let span = item.sig.ident.span();
+ let valid_signature = util::check_foreign_fn_signature(&item, false)
+ && item.sig.inputs.len() == 1
+ && util::type_is_unit(&item.sig.output);
+
+ let name = item.sig.ident.to_string();
+
+ if name == "init" || name == "idle" {
+ return Err(parse::Error::new(
+ span,
+ "tasks cannot be named `init` or `idle`",
+ ));
+ }
+
+ if valid_signature {
+ if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
+ if rest.is_empty() {
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
+
+ return Ok(HardwareTask {
+ args,
+ cfgs,
+ attrs,
+ context,
+ stmts: Vec::<Stmt>::new(),
+ is_extern: true,
+ });
+ }
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "this task handler must have type signature `fn({}::Context)`",
+ name
+ ),
+ ))
+ }
+}
diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs
new file mode 100644
index 00000000..d9f3a99e
--- /dev/null
+++ b/macros/src/syntax/parse/idle.rs
@@ -0,0 +1,45 @@
+use proc_macro2::TokenStream as TokenStream2;
+use syn::{parse, ItemFn};
+
+use crate::syntax::{
+ ast::{Idle, IdleArgs},
+ parse::util,
+};
+
+impl IdleArgs {
+ pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
+ crate::syntax::parse::idle_args(tokens)
+ }
+}
+
+impl Idle {
+ pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result<Self> {
+ let valid_signature = util::check_fn_signature(&item, false)
+ && item.sig.inputs.len() == 1
+ && util::type_is_bottom(&item.sig.output);
+
+ let name = item.sig.ident.to_string();
+
+ if valid_signature {
+ if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
+ if rest.is_empty() {
+ return Ok(Idle {
+ args,
+ attrs: item.attrs,
+ context,
+ name: item.sig.ident,
+ stmts: item.block.stmts,
+ });
+ }
+ }
+ }
+
+ Err(parse::Error::new(
+ item.sig.ident.span(),
+ &format!(
+ "this `#[idle]` function must have signature `fn({}::Context) -> !`",
+ name
+ ),
+ ))
+ }
+}
diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs
new file mode 100644
index 00000000..727ee205
--- /dev/null
+++ b/macros/src/syntax/parse/init.rs
@@ -0,0 +1,52 @@
+use proc_macro2::TokenStream as TokenStream2;
+
+use syn::{parse, ItemFn};
+
+use crate::syntax::{
+ ast::{Init, InitArgs},
+ parse::{self as syntax_parse, util},
+};
+
+impl InitArgs {
+ pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
+ syntax_parse::init_args(tokens)
+ }
+}
+
+impl Init {
+ pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result<Self> {
+ let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1;
+
+ let span = item.sig.ident.span();
+
+ let name = item.sig.ident.to_string();
+
+ if valid_signature {
+ if let Ok((user_shared_struct, user_local_struct)) =
+ util::type_is_init_return(&item.sig.output, &name)
+ {
+ if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
+ if rest.is_empty() {
+ return Ok(Init {
+ args,
+ attrs: item.attrs,
+ context,
+ name: item.sig.ident,
+ stmts: item.block.stmts,
+ user_shared_struct,
+ user_local_struct,
+ });
+ }
+ }
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "the `#[init]` function must have signature `fn({}::Context) -> (Shared resources struct, Local resources struct, {0}::Monotonics)`",
+ name
+ ),
+ ))
+ }
+}
diff --git a/macros/src/syntax/parse/monotonic.rs b/macros/src/syntax/parse/monotonic.rs
new file mode 100644
index 00000000..05832339
--- /dev/null
+++ b/macros/src/syntax/parse/monotonic.rs
@@ -0,0 +1,42 @@
+use proc_macro2::Span;
+use syn::Attribute;
+use syn::{parse, spanned::Spanned, ItemType, Visibility};
+
+use crate::syntax::parse::util::FilterAttrs;
+use crate::syntax::{
+ ast::{Monotonic, MonotonicArgs},
+ parse::util,
+};
+
+impl MonotonicArgs {
+ pub(crate) fn parse(attr: Attribute) -> parse::Result<Self> {
+ crate::syntax::parse::monotonic_args(attr.path, attr.tokens)
+ }
+}
+
+impl Monotonic {
+ pub(crate) fn parse(args: MonotonicArgs, item: &ItemType, span: Span) -> parse::Result<Self> {
+ if item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ span,
+ "this field must have inherited / private visibility",
+ ));
+ }
+
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs.clone());
+
+ if !attrs.is_empty() {
+ return Err(parse::Error::new(
+ attrs[0].path.span(),
+ "Monotonic does not support attributes other than `#[cfg]`",
+ ));
+ }
+
+ Ok(Monotonic {
+ cfgs,
+ ident: item.ident.clone(),
+ ty: item.ty.clone(),
+ args,
+ })
+ }
+}
diff --git a/macros/src/syntax/parse/resource.rs b/macros/src/syntax/parse/resource.rs
new file mode 100644
index 00000000..ff100576
--- /dev/null
+++ b/macros/src/syntax/parse/resource.rs
@@ -0,0 +1,55 @@
+use proc_macro2::Span;
+use syn::{parse, Field, Visibility};
+
+use crate::syntax::parse::util::FilterAttrs;
+use crate::syntax::{
+ ast::{LocalResource, SharedResource, SharedResourceProperties},
+ parse::util,
+};
+
+impl SharedResource {
+ pub(crate) fn parse(item: &Field, span: Span) -> parse::Result<Self> {
+ if item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ span,
+ "this field must have inherited / private visibility",
+ ));
+ }
+
+ let FilterAttrs {
+ cfgs,
+ mut attrs,
+ docs,
+ } = util::filter_attributes(item.attrs.clone());
+
+ let lock_free = util::extract_lock_free(&mut attrs)?;
+
+ Ok(SharedResource {
+ cfgs,
+ attrs,
+ docs,
+ ty: Box::new(item.ty.clone()),
+ properties: SharedResourceProperties { lock_free },
+ })
+ }
+}
+
+impl LocalResource {
+ pub(crate) fn parse(item: &Field, span: Span) -> parse::Result<Self> {
+ if item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ span,
+ "this field must have inherited / private visibility",
+ ));
+ }
+
+ let FilterAttrs { cfgs, attrs, docs } = util::filter_attributes(item.attrs.clone());
+
+ Ok(LocalResource {
+ cfgs,
+ attrs,
+ docs,
+ ty: Box::new(item.ty.clone()),
+ })
+ }
+}
diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs
new file mode 100644
index 00000000..2b1ac4a5
--- /dev/null
+++ b/macros/src/syntax/parse/software_task.rs
@@ -0,0 +1,86 @@
+use syn::{parse, ForeignItemFn, ItemFn, Stmt};
+
+use crate::syntax::parse::util::FilterAttrs;
+use crate::syntax::{
+ ast::{SoftwareTask, SoftwareTaskArgs},
+ parse::util,
+};
+
+impl SoftwareTask {
+ pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result<Self> {
+ let valid_signature =
+ util::check_fn_signature(&item, true) && util::type_is_unit(&item.sig.output);
+
+ let span = item.sig.ident.span();
+
+ let name = item.sig.ident.to_string();
+
+ let is_async = item.sig.asyncness.is_some();
+
+ if valid_signature {
+ if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) {
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
+
+ return Ok(SoftwareTask {
+ args,
+ attrs,
+ cfgs,
+ context,
+ inputs,
+ stmts: item.block.stmts,
+ is_extern: false,
+ is_async,
+ });
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "this task handler must have type signature `(async) fn({}::Context, ..)`",
+ name
+ ),
+ ))
+ }
+}
+
+impl SoftwareTask {
+ pub(crate) fn parse_foreign(
+ args: SoftwareTaskArgs,
+ item: ForeignItemFn,
+ ) -> parse::Result<Self> {
+ let valid_signature =
+ util::check_foreign_fn_signature(&item, true) && util::type_is_unit(&item.sig.output);
+
+ let span = item.sig.ident.span();
+
+ let name = item.sig.ident.to_string();
+
+ let is_async = item.sig.asyncness.is_some();
+
+ if valid_signature {
+ if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) {
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
+
+ return Ok(SoftwareTask {
+ args,
+ attrs,
+ cfgs,
+ context,
+ inputs,
+ stmts: Vec::<Stmt>::new(),
+ is_extern: true,
+ is_async,
+ });
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "this task handler must have type signature `(async) fn({}::Context, ..)`",
+ name
+ ),
+ ))
+ }
+}
diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs
new file mode 100644
index 00000000..3fa51ef8
--- /dev/null
+++ b/macros/src/syntax/parse/util.rs
@@ -0,0 +1,338 @@
+use syn::{
+ bracketed,
+ parse::{self, ParseStream},
+ punctuated::Punctuated,
+ spanned::Spanned,
+ Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path,
+ PathArguments, ReturnType, Token, Type, Visibility,
+};
+
+use crate::syntax::{
+ ast::{Access, Local, LocalResources, SharedResources, TaskLocal},
+ Map,
+};
+
+pub fn abi_is_rust(abi: &Abi) -> bool {
+ match &abi.name {
+ None => true,
+ Some(s) => s.value() == "Rust",
+ }
+}
+
+pub fn attr_eq(attr: &Attribute, name: &str) -> bool {
+ attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && {
+ let segment = attr.path.segments.first().unwrap();
+ segment.arguments == PathArguments::None && *segment.ident.to_string() == *name
+ }
+}
+
+/// checks that a function signature
+///
+/// - has no bounds (like where clauses)
+/// - is not `async`
+/// - is not `const`
+/// - is not `unsafe`
+/// - is not generic (has no type parameters)
+/// - is not variadic
+/// - uses the Rust ABI (and not e.g. "C")
+pub fn check_fn_signature(item: &ItemFn, allow_async: bool) -> bool {
+ item.vis == Visibility::Inherited
+ && item.sig.constness.is_none()
+ && (item.sig.asyncness.is_none() || allow_async)
+ && item.sig.abi.is_none()
+ && item.sig.unsafety.is_none()
+ && item.sig.generics.params.is_empty()
+ && item.sig.generics.where_clause.is_none()
+ && item.sig.variadic.is_none()
+}
+
+#[allow(dead_code)]
+pub fn check_foreign_fn_signature(item: &ForeignItemFn, allow_async: bool) -> bool {
+ item.vis == Visibility::Inherited
+ && item.sig.constness.is_none()
+ && (item.sig.asyncness.is_none() || allow_async)
+ && item.sig.abi.is_none()
+ && item.sig.unsafety.is_none()
+ && item.sig.generics.params.is_empty()
+ && item.sig.generics.where_clause.is_none()
+ && item.sig.variadic.is_none()
+}
+
+pub struct FilterAttrs {
+ pub cfgs: Vec<Attribute>,
+ pub docs: Vec<Attribute>,
+ pub attrs: Vec<Attribute>,
+}
+
+pub fn filter_attributes(input_attrs: Vec<Attribute>) -> FilterAttrs {
+ let mut cfgs = vec![];
+ let mut docs = vec![];
+ let mut attrs = vec![];
+
+ for attr in input_attrs {
+ if attr_eq(&attr, "cfg") {
+ cfgs.push(attr);
+ } else if attr_eq(&attr, "doc") {
+ docs.push(attr);
+ } else {
+ attrs.push(attr);
+ }
+ }
+
+ FilterAttrs { cfgs, docs, attrs }
+}
+
+pub fn extract_lock_free(attrs: &mut Vec<Attribute>) -> parse::Result<bool> {
+ if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) {
+ attrs.remove(pos);
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+}
+
+pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result<SharedResources> {
+ let inner;
+ bracketed!(inner in content);
+
+ let mut resources = Map::new();
+ for e in inner.call(Punctuated::<Expr, Token![,]>::parse_terminated)? {
+ let err = Err(parse::Error::new(
+ e.span(),
+ "identifier appears more than once in list",
+ ));
+ let (access, path) = match e {
+ Expr::Path(e) => (Access::Exclusive, e.path),
+
+ Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr {
+ Expr::Path(e) => (Access::Shared, e.path.clone()),
+
+ _ => return err,
+ },
+
+ _ => return err,
+ };
+
+ let ident = extract_resource_name_ident(path)?;
+
+ if resources.contains_key(&ident) {
+ return Err(parse::Error::new(
+ ident.span(),
+ "resource appears more than once in list",
+ ));
+ }
+
+ resources.insert(ident, access);
+ }
+
+ Ok(resources)
+}
+
+fn extract_resource_name_ident(path: Path) -> parse::Result<Ident> {
+ if path.leading_colon.is_some()
+ || path.segments.len() != 1
+ || path.segments[0].arguments != PathArguments::None
+ {
+ Err(parse::Error::new(
+ path.span(),
+ "resource must be an identifier, not a path",
+ ))
+ } else {
+ Ok(path.segments[0].ident.clone())
+ }
+}
+
+pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result<LocalResources> {
+ let inner;
+ bracketed!(inner in content);
+
+ let mut resources = Map::new();
+
+ for e in inner.call(Punctuated::<Expr, Token![,]>::parse_terminated)? {
+ let err = Err(parse::Error::new(
+ e.span(),
+ "identifier appears more than once in list",
+ ));
+
+ let (name, local) = match e {
+ // local = [IDENT],
+ Expr::Path(path) => {
+ if !path.attrs.is_empty() {
+ return Err(parse::Error::new(
+ path.span(),
+ "attributes are not supported here",
+ ));
+ }
+
+ let ident = extract_resource_name_ident(path.path)?;
+ // let (cfgs, attrs) = extract_cfgs(path.attrs);
+
+ (ident, TaskLocal::External)
+ }
+
+ // local = [IDENT: TYPE = EXPR]
+ Expr::Assign(e) => {
+ let (name, ty, cfgs, attrs) = match *e.left {
+ Expr::Type(t) => {
+ // Extract name and attributes
+ let (name, cfgs, attrs) = match *t.expr {
+ Expr::Path(path) => {
+ let name = extract_resource_name_ident(path.path)?;
+ let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs);
+
+ (name, cfgs, attrs)
+ }
+ _ => return err,
+ };
+
+ let ty = t.ty;
+
+ // Error check
+ match &*ty {
+ Type::Array(_) => {}
+ Type::Path(_) => {}
+ Type::Ptr(_) => {}
+ Type::Tuple(_) => {}
+ _ => return Err(parse::Error::new(
+ ty.span(),
+ "unsupported type, must be an array, tuple, pointer or type path",
+ )),
+ };
+
+ (name, ty, cfgs, attrs)
+ }
+ e => return Err(parse::Error::new(e.span(), "malformed, expected a type")),
+ };
+
+ let expr = e.right; // Expr
+
+ (
+ name,
+ TaskLocal::Declared(Local {
+ attrs,
+ cfgs,
+ ty,
+ expr,
+ }),
+ )
+ }
+
+ expr => {
+ return Err(parse::Error::new(
+ expr.span(),
+ "malformed, expected 'IDENT: TYPE = EXPR'",
+ ))
+ }
+ };
+
+ resources.insert(name, local);
+ }
+
+ Ok(resources)
+}
+
+type ParseInputResult = Option<(Box<Pat>, Result<Vec<PatType>, FnArg>)>;
+
+pub fn parse_inputs(inputs: Punctuated<FnArg, Token![,]>, name: &str) -> ParseInputResult {
+ let mut inputs = inputs.into_iter();
+
+ match inputs.next() {
+ Some(FnArg::Typed(first)) => {
+ if type_is_path(&first.ty, &[name, "Context"]) {
+ let rest = inputs
+ .map(|arg| match arg {
+ FnArg::Typed(arg) => Ok(arg),
+ _ => Err(arg),
+ })
+ .collect::<Result<Vec<_>, _>>();
+
+ Some((first.pat, rest))
+ } else {
+ None
+ }
+ }
+
+ _ => None,
+ }
+}
+
+pub fn type_is_bottom(ty: &ReturnType) -> bool {
+ if let ReturnType::Type(_, ty) = ty {
+ matches!(**ty, Type::Never(_))
+ } else {
+ false
+ }
+}
+
+fn extract_init_resource_name_ident(ty: Type) -> Result<Ident, ()> {
+ match ty {
+ Type::Path(path) => {
+ let path = path.path;
+
+ if path.leading_colon.is_some()
+ || path.segments.len() != 1
+ || path.segments[0].arguments != PathArguments::None
+ {
+ Err(())
+ } else {
+ Ok(path.segments[0].ident.clone())
+ }
+ }
+ _ => Err(()),
+ }
+}
+
+/// Checks Init's return type, return the user provided types for analysis
+pub fn type_is_init_return(ty: &ReturnType, name: &str) -> Result<(Ident, Ident), ()> {
+ match ty {
+ ReturnType::Default => Err(()),
+
+ ReturnType::Type(_, ty) => match &**ty {
+ Type::Tuple(t) => {
+ // return should be:
+ // fn -> (User's #[shared] struct, User's #[local] struct, {name}::Monotonics)
+ //
+ // We check the length and the last one here, analysis checks that the user
+ // provided structs are correct.
+ if t.elems.len() == 3 && type_is_path(&t.elems[2], &[name, "Monotonics"]) {
+ return Ok((
+ extract_init_resource_name_ident(t.elems[0].clone())?,
+ extract_init_resource_name_ident(t.elems[1].clone())?,
+ ));
+ }
+
+ Err(())
+ }
+
+ _ => Err(()),
+ },
+ }
+}
+
+pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool {
+ match ty {
+ Type::Path(tpath) if tpath.qself.is_none() => {
+ tpath.path.segments.len() == segments.len()
+ && tpath
+ .path
+ .segments
+ .iter()
+ .zip(segments)
+ .all(|(lhs, rhs)| lhs.ident == **rhs)
+ }
+
+ _ => false,
+ }
+}
+
+pub fn type_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
+ }
+}