rtic/rtic-macros/src/syntax/parse.rs
Olle Ronstad 11d2ca9f2b added argument trampoline to hardware tasks to allow exception "masking"
Co-authored-by: Salon <salon64@users.noreply.github.com>
2025-10-06 12:50:19 +02:00

340 lines
10 KiB
Rust

mod app;
mod hardware_task;
mod idle;
mod init;
mod resource;
mod software_task;
mod util;
use proc_macro2::TokenStream as TokenStream2;
use syn::{
braced,
parse::{self, Parse, ParseStream, Parser},
token::Brace,
Attribute, Ident, Item, LitInt, Meta, Token,
};
use crate::syntax::{
ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal},
Either,
};
// Parse the app, both app arguments and body (input)
pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result<App> {
let args = AppArgs::parse(args)?;
let input: Input = syn::parse2(input)?;
App::parse(args, input)
}
pub(crate) struct Input {
pub attribute_metas: Vec<Meta>,
_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 mut attributes = input.call(Attribute::parse_outer)?;
let _mod_token = input.parse()?;
let ident = input.parse()?;
let _brace_token = braced!(content in input);
let inner_attributes = content.call(Attribute::parse_inner)?;
let items = content.call(parse_items)?;
attributes.extend(inner_attributes);
let attribute_metas = attributes.into_iter().map(|a| a.meta).collect();
Ok(Input {
attribute_metas,
_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;
if !input.is_empty() {
loop {
// Parse identifier name
let ident: Ident = input.parse()?;
// Handle equal sign
let _: Token![=] = input.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(input)?);
}
_ => {
return Err(parse::Error::new(ident.span(), "unexpected argument"));
}
}
if input.is_empty() {
break;
}
// Handle comma: ,
let _: Token![,] = input.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;
if !input.is_empty() {
loop {
// Parse identifier name
let ident: Ident = input.parse()?;
// Handle equal sign
let _: Token![=] = input.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(input)?);
}
"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(input)?);
}
_ => {
return Err(parse::Error::new(ident.span(), "unexpected argument"));
}
}
if input.is_empty() {
break;
}
// Handle comma: ,
let _: Token![,] = input.parse()?;
}
}
Ok(IdleArgs {
shared_resources: shared_resources.unwrap_or_default(),
local_resources: local_resources.unwrap_or_default(),
})
})
.parse2(tokens)
}
fn task_args(tokens: TokenStream2) -> 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 trampoline = None;
let mut priority = None;
let mut shared_resources = None;
let mut local_resources = None;
let mut prio_span = None;
loop {
if input.is_empty() {
break;
}
// Parse identifier name
let ident: Ident = input.parse()?;
let ident_s = ident.to_string();
// Handle equal sign
let _: Token![=] = input.parse()?;
match &*ident_s {
"binds" => {
if binds.is_some() {
return Err(parse::Error::new(
ident.span(),
"argument appears more than once",
));
}
// Parse identifier name
let ident = input.parse()?;
binds = Some(ident);
},
"trampoline" => {
if trampoline.is_some() {
return Err(parse::Error::new(
ident.span(),
"argument appears more than once",
));
}
// Parse identifier name
let ident: Ident = input.parse()?;
trampoline = Some(ident);
},
"priority" => {
if priority.is_some() {
return Err(parse::Error::new(
ident.span(),
"argument appears more than once",
));
}
// #lit
let lit: LitInt = input.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(input)?);
}
"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(input)?);
}
a => {
return Err(parse::Error::new(ident.span(), format!("unexpected argument {}", a)));
}
}
if input.is_empty() {
break;
}
// Handle comma: ,
let _: Token![,] = input.parse()?;
}
let shared_resources = shared_resources.unwrap_or_default();
let local_resources = local_resources.unwrap_or_default();
Ok(if let Some(binds) = binds {
// Hardware tasks can't run at anything lower than 1
let priority = priority.unwrap_or(1);
if priority == 0 {
return Err(parse::Error::new(
prio_span.unwrap(),
"hardware tasks are not allowed to be at priority 0",
));
}
Either::Left(HardwareTaskArgs {
binds,
priority,
shared_resources,
local_resources,
trampoline,
})
} else {
// Software tasks start at idle priority
let priority = priority.unwrap_or(0);
Either::Right(SoftwareTaskArgs {
priority,
shared_resources,
local_resources,
})
})
})
.parse2(tokens)
}