aboutsummaryrefslogtreecommitdiff
path: root/macros/src/syntax/parse.rs
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src/syntax/parse.rs')
-rw-r--r--macros/src/syntax/parse.rs520
1 files changed, 520 insertions, 0 deletions
diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs
new file mode 100644
index 00000000..74f94f2b
--- /dev/null
+++ b/macros/src/syntax/parse.rs
@@ -0,0 +1,520 @@
+mod app;
+mod hardware_task;
+mod idle;
+mod init;
+mod monotonic;
+mod resource;
+mod software_task;
+mod util;
+
+use proc_macro2::TokenStream as TokenStream2;
+use syn::{
+ braced, parenthesized,
+ parse::{self, Parse, ParseStream, Parser},
+ token::{self, Brace},
+ Ident, Item, LitBool, LitInt, Path, Token,
+};
+
+use crate::syntax::{
+ ast::{
+ App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, MonotonicArgs, SoftwareTaskArgs,
+ TaskLocal,
+ },
+ Either, Settings,
+};
+
+// Parse the app, both app arguments and body (input)
+pub fn app(args: TokenStream2, input: TokenStream2, settings: &Settings) -> parse::Result<App> {
+ let args = AppArgs::parse(args)?;
+ let input: Input = syn::parse2(input)?;
+
+ App::parse(args, input, settings)
+}
+
+pub(crate) struct Input {
+ _mod_token: Token![mod],
+ pub ident: Ident,
+ _brace_token: Brace,
+ pub items: Vec<Item>,
+}
+
+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;
+
+ let _mod_token = input.parse()?;
+ let ident = input.parse()?;
+ let _brace_token = braced!(content in input);
+ let items = content.call(parse_items)?;
+
+ Ok(Input {
+ _mod_token,
+ ident,
+ _brace_token,
+ items,
+ })
+ }
+}
+
+fn init_args(tokens: TokenStream2) -> parse::Result<InitArgs> {
+ (|input: ParseStream<'_>| -> parse::Result<InitArgs> {
+ if input.is_empty() {
+ return Ok(InitArgs::default());
+ }
+
+ let mut local_resources = None;
+
+ let content;
+ parenthesized!(content in input);
+
+ if !content.is_empty() {
+ loop {
+ // Parse identifier name
+ let ident: Ident = content.parse()?;
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident.to_string() {
+ "local" => {
+ if local_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ local_resources = Some(util::parse_local_resources(&content)?);
+ }
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+
+ if content.is_empty() {
+ break;
+ }
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+ }
+ }
+
+ if let Some(locals) = &local_resources {
+ for (ident, task_local) in locals {
+ if let TaskLocal::External = task_local {
+ return Err(parse::Error::new(
+ ident.span(),
+ "only declared local resources are allowed in init",
+ ));
+ }
+ }
+ }
+
+ Ok(InitArgs {
+ local_resources: local_resources.unwrap_or_default(),
+ })
+ })
+ .parse2(tokens)
+}
+
+fn idle_args(tokens: TokenStream2) -> parse::Result<IdleArgs> {
+ (|input: ParseStream<'_>| -> parse::Result<IdleArgs> {
+ if input.is_empty() {
+ return Ok(IdleArgs::default());
+ }
+
+ let mut shared_resources = None;
+ let mut local_resources = None;
+
+ let content;
+ parenthesized!(content in input);
+ if !content.is_empty() {
+ loop {
+ // Parse identifier name
+ let ident: Ident = content.parse()?;
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident.to_string() {
+ "shared" => {
+ if shared_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ shared_resources = Some(util::parse_shared_resources(&content)?);
+ }
+
+ "local" => {
+ if local_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ local_resources = Some(util::parse_local_resources(&content)?);
+ }
+
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+ if content.is_empty() {
+ break;
+ }
+
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+ }
+ }
+
+ Ok(IdleArgs {
+ shared_resources: shared_resources.unwrap_or_default(),
+ local_resources: local_resources.unwrap_or_default(),
+ })
+ })
+ .parse2(tokens)
+}
+
+fn task_args(
+ tokens: TokenStream2,
+ settings: &Settings,
+) -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
+ (|input: ParseStream<'_>| -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
+ if input.is_empty() {
+ return Ok(Either::Right(SoftwareTaskArgs::default()));
+ }
+
+ let mut binds = None;
+ let mut capacity = None;
+ let mut priority = None;
+ let mut shared_resources = None;
+ let mut local_resources = None;
+ let mut prio_span = None;
+ let mut only_same_priority_spawn = false;
+ let mut only_same_prio_span = None;
+
+ let content;
+ parenthesized!(content in input);
+ loop {
+ if content.is_empty() {
+ break;
+ }
+
+ // Parse identifier name
+ let ident: Ident = content.parse()?;
+ let ident_s = ident.to_string();
+
+ if ident_s == "only_same_priority_spawn_please_fix_me" {
+ if only_same_priority_spawn {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ only_same_priority_spawn = true;
+ only_same_prio_span = Some(ident.span());
+
+ if content.is_empty() {
+ break;
+ }
+
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+
+ continue;
+ }
+
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident_s {
+ "binds" if !settings.parse_binds => {
+ return Err(parse::Error::new(
+ ident.span(),
+ "Unexpected bind in task argument. Binds are only parsed if Settings::parse_binds is set.",
+ ));
+ }
+
+ "binds" if settings.parse_binds => {
+ if binds.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ if capacity.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "hardware tasks can't use the `capacity` argument",
+ ));
+ }
+
+ // Parse identifier name
+ let ident = content.parse()?;
+
+ binds = Some(ident);
+ }
+
+ "capacity" => {
+ if capacity.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ if binds.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "hardware tasks can't use the `capacity` argument",
+ ));
+ }
+
+ // #lit
+ let lit: LitInt = content.parse()?;
+
+ if !lit.suffix().is_empty() {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be unsuffixed",
+ ));
+ }
+
+ let value = lit.base10_parse::<u8>().ok();
+ if value.is_none() || value == Some(0) {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be in the range 1...255",
+ ));
+ }
+
+ capacity = Some(value.unwrap());
+ }
+
+ "priority" => {
+ if priority.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ // #lit
+ let lit: LitInt = content.parse()?;
+
+ if !lit.suffix().is_empty() {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be unsuffixed",
+ ));
+ }
+
+ let value = lit.base10_parse::<u8>().ok();
+ if value.is_none() {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be in the range 0...255",
+ ));
+ }
+
+ prio_span = Some(lit.span());
+ priority = Some(value.unwrap());
+ }
+
+ "shared" => {
+ if shared_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ shared_resources = Some(util::parse_shared_resources(&content)?);
+ }
+
+ "local" => {
+ if local_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ local_resources = Some(util::parse_local_resources(&content)?);
+ }
+
+
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+
+ if content.is_empty() {
+ break;
+ }
+
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+ }
+ let priority = priority.unwrap_or(1);
+ let shared_resources = shared_resources.unwrap_or_default();
+ let local_resources = local_resources.unwrap_or_default();
+
+ Ok(if let Some(binds) = binds {
+ if priority == 0 {
+ return Err(parse::Error::new(
+ prio_span.unwrap(),
+ "hardware tasks are not allowed to be at priority 0",
+ ));
+ }
+
+ if only_same_priority_spawn {
+ return Err(parse::Error::new(
+ only_same_prio_span.unwrap(),
+ "hardware tasks are not allowed to be spawned, `only_same_priority_spawn_please_fix_me` is only for software tasks",
+ ));
+ }
+
+ Either::Left(HardwareTaskArgs {
+ binds,
+ priority,
+ shared_resources,
+ local_resources,
+ })
+ } else {
+ Either::Right(SoftwareTaskArgs {
+ capacity: capacity.unwrap_or(1),
+ priority,
+ shared_resources,
+ local_resources,
+ only_same_priority_spawn,
+ })
+ })
+ })
+ .parse2(tokens)
+}
+
+fn monotonic_args(path: Path, tokens: TokenStream2) -> parse::Result<MonotonicArgs> {
+ (|input: ParseStream<'_>| -> parse::Result<MonotonicArgs> {
+ let mut binds = None;
+ let mut priority = None;
+ let mut default = None;
+
+ if !input.peek(token::Paren) {
+ return Err(parse::Error::new(
+ path.segments.first().unwrap().ident.span(),
+ "expected opening ( in #[monotonic( ... )]",
+ ));
+ }
+
+ let content;
+ parenthesized!(content in input);
+
+ if !content.is_empty() {
+ loop {
+ // Parse identifier name
+ let ident: Ident = content.parse()?;
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident.to_string() {
+ "binds" => {
+ if binds.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+ // Parse identifier name
+ let ident = content.parse()?;
+
+ binds = Some(ident);
+ }
+
+ "priority" => {
+ if priority.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ // #lit
+ let lit: LitInt = content.parse()?;
+
+ if !lit.suffix().is_empty() {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be unsuffixed",
+ ));
+ }
+
+ let value = lit.base10_parse::<u8>().ok();
+ if value.is_none() || value == Some(0) {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be in the range 1...255",
+ ));
+ }
+
+ priority = Some(value.unwrap());
+ }
+
+ "default" => {
+ if default.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ let lit: LitBool = content.parse()?;
+ default = Some(lit.value);
+ }
+
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+ if content.is_empty() {
+ break;
+ }
+
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+ }
+ }
+
+ let binds = if let Some(r) = binds {
+ r
+ } else {
+ return Err(parse::Error::new(
+ content.span(),
+ "`binds = ...` is missing",
+ ));
+ };
+ let default = default.unwrap_or(false);
+
+ Ok(MonotonicArgs {
+ binds,
+ priority,
+ default,
+ })
+ })
+ .parse2(tokens)
+}