Move rtic macros to repo root, tune xtask

This commit is contained in:
Henrik Tjäder 2023-02-04 16:47:17 +01:00
parent 4124fbdd61
commit 9e445b3583
134 changed files with 31 additions and 29 deletions

View file

@ -0,0 +1,31 @@
language: rust
matrix:
include:
# MSRV
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.36.0
- env: TARGET=x86_64-unknown-linux-gnu
rust: stable
before_install: set -e
script:
- bash ci/script.sh
after_script: set +e
cache: cargo
before_cache:
- chmod -R a+r $HOME/.cargo;
branches:
only:
- staging
- trying
notifications:
email:
on_success: never

View file

@ -0,0 +1,113 @@
use syn::Ident;
use crate::syntax::{
analyze::Priority,
ast::{Access, App, Local, TaskLocal},
};
impl App {
pub(crate) fn shared_resource_accesses(
&self,
) -> impl Iterator<Item = (Option<Priority>, &Ident, Access)> {
self.idle
.iter()
.flat_map(|idle| {
idle.args
.shared_resources
.iter()
.map(move |(name, access)| (Some(0), name, *access))
})
.chain(self.hardware_tasks.values().flat_map(|task| {
task.args
.shared_resources
.iter()
.map(move |(name, access)| (Some(task.args.priority), name, *access))
}))
.chain(self.software_tasks.values().flat_map(|task| {
task.args
.shared_resources
.iter()
.map(move |(name, access)| (Some(task.args.priority), name, *access))
}))
}
fn is_external(task_local: &TaskLocal) -> bool {
matches!(task_local, TaskLocal::External)
}
pub(crate) fn local_resource_accesses(&self) -> impl Iterator<Item = &Ident> {
self.init
.args
.local_resources
.iter()
.filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]`
.map(move |(name, _)| name)
.chain(self.idle.iter().flat_map(|idle| {
idle.args
.local_resources
.iter()
.filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]`
.map(move |(name, _)| name)
}))
.chain(self.hardware_tasks.values().flat_map(|task| {
task.args
.local_resources
.iter()
.filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]`
.map(move |(name, _)| name)
}))
.chain(self.software_tasks.values().flat_map(|task| {
task.args
.local_resources
.iter()
.filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]`
.map(move |(name, _)| name)
}))
}
fn get_declared_local(tl: &TaskLocal) -> Option<&Local> {
match tl {
TaskLocal::External => None,
TaskLocal::Declared(l) => Some(l),
}
}
/// Get all declared local resources, i.e. `local = [NAME: TYPE = EXPR]`.
///
/// Returns a vector of (task name, resource name, `Local` struct)
pub fn declared_local_resources(&self) -> Vec<(&Ident, &Ident, &Local)> {
self.init
.args
.local_resources
.iter()
.filter_map(move |(name, tl)| {
Self::get_declared_local(tl).map(|l| (&self.init.name, name, l))
})
.chain(self.idle.iter().flat_map(|idle| {
idle.args
.local_resources
.iter()
.filter_map(move |(name, tl)| {
Self::get_declared_local(tl)
.map(|l| (&self.idle.as_ref().unwrap().name, name, l))
})
}))
.chain(self.hardware_tasks.iter().flat_map(|(task_name, task)| {
task.args
.local_resources
.iter()
.filter_map(move |(name, tl)| {
Self::get_declared_local(tl).map(|l| (task_name, name, l))
})
}))
.chain(self.software_tasks.iter().flat_map(|(task_name, task)| {
task.args
.local_resources
.iter()
.filter_map(move |(name, tl)| {
Self::get_declared_local(tl).map(|l| (task_name, name, l))
})
}))
.collect()
}
}

View file

@ -0,0 +1,414 @@
//! RTIC application analysis
use core::cmp;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use indexmap::{IndexMap, IndexSet};
use syn::{Ident, Type};
use crate::syntax::{
ast::{App, LocalResources, TaskLocal},
Set,
};
pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
// Collect all tasks into a vector
type TaskName = Ident;
type Priority = u8;
// The task list is a Tuple (Name, Shared Resources, Local Resources, Priority)
let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> =
Some(&app.init)
.iter()
.map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0))
.chain(app.idle.iter().map(|ht| {
(
ht.name.clone(),
ht.args
.shared_resources
.iter()
.map(|(v, _)| v)
.collect::<Vec<_>>(),
&ht.args.local_resources,
0,
)
}))
.chain(app.software_tasks.iter().map(|(name, ht)| {
(
name.clone(),
ht.args
.shared_resources
.iter()
.map(|(v, _)| v)
.collect::<Vec<_>>(),
&ht.args.local_resources,
ht.args.priority,
)
}))
.chain(app.hardware_tasks.iter().map(|(name, ht)| {
(
name.clone(),
ht.args
.shared_resources
.iter()
.map(|(v, _)| v)
.collect::<Vec<_>>(),
&ht.args.local_resources,
ht.args.priority,
)
}))
.collect();
let mut error = vec![];
let mut lf_res_with_error = vec![];
let mut lf_hash = HashMap::new();
// Collect lock free resources
let lock_free: Vec<&Ident> = app
.shared_resources
.iter()
.filter(|(_, r)| r.properties.lock_free)
.map(|(i, _)| i)
.collect();
// Check that lock_free resources are correct
for lf_res in lock_free.iter() {
for (task, tr, _, priority) in task_resources_list.iter() {
for r in tr {
// Get all uses of resources annotated lock_free
if lf_res == r {
// Check so async tasks do not use lock free resources
if app.software_tasks.get(task).is_some() {
error.push(syn::Error::new(
r.span(),
format!(
"Lock free shared resource {:?} is used by an async tasks, which is forbidden",
r.to_string(),
),
));
}
// HashMap returns the previous existing object if old.key == new.key
if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) {
// Check if priority differ, if it does, append to
// list of resources which will be annotated with errors
if priority != lf_res.2 {
lf_res_with_error.push(lf_res.1);
lf_res_with_error.push(r);
}
// If the resource already violates lock free properties
if lf_res_with_error.contains(&r) {
lf_res_with_error.push(lf_res.1);
lf_res_with_error.push(r);
}
}
}
}
}
}
// Add error message in the resource struct
for r in lock_free {
if lf_res_with_error.contains(&&r) {
error.push(syn::Error::new(
r.span(),
format!(
"Lock free shared resource {:?} is used by tasks at different priorities",
r.to_string(),
),
));
}
}
// Add error message for each use of the shared resource
for resource in lf_res_with_error.clone() {
error.push(syn::Error::new(
resource.span(),
format!(
"Shared resource {:?} is declared lock free but used by tasks at different priorities",
resource.to_string(),
),
));
}
// Collect local resources
let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect();
let mut lr_with_error = vec![];
let mut lr_hash = HashMap::new();
// Check that local resources are not shared
for lr in local {
for (task, _, local_resources, _) in task_resources_list.iter() {
for (name, res) in local_resources.iter() {
// Get all uses of resources annotated lock_free
if lr == name {
match res {
TaskLocal::External => {
// HashMap returns the previous existing object if old.key == new.key
if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) {
lr_with_error.push(lr.1);
lr_with_error.push(name);
}
}
// If a declared local has the same name as the `#[local]` struct, it's an
// direct error
TaskLocal::Declared(_) => {
lr_with_error.push(lr);
lr_with_error.push(name);
}
}
}
}
}
}
// Add error message for each use of the local resource
for resource in lr_with_error.clone() {
error.push(syn::Error::new(
resource.span(),
format!(
"Local resource {:?} is used by multiple tasks or collides with multiple definitions",
resource.to_string(),
),
));
}
// Check 0-priority async software tasks and idle dependency
for (name, task) in &app.software_tasks {
if task.args.priority == 0 {
// If there is a 0-priority task, there must be no idle
if app.idle.is_some() {
error.push(syn::Error::new(
name.span(),
format!(
"Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.",
name.to_string(),
)
));
}
}
}
// Collect errors if any and return/halt
if !error.is_empty() {
let mut err = error.get(0).unwrap().clone();
error.iter().for_each(|e| err.combine(e.clone()));
return Err(err);
}
// e. Location of resources
let mut used_shared_resource = IndexSet::new();
let mut ownerships = Ownerships::new();
let mut sync_types = SyncTypes::new();
for (prio, name, access) in app.shared_resource_accesses() {
let res = app.shared_resources.get(name).expect("UNREACHABLE");
// (e)
// This shared resource is used
used_shared_resource.insert(name.clone());
// (c)
if let Some(priority) = prio {
if let Some(ownership) = ownerships.get_mut(name) {
match *ownership {
Ownership::Owned { priority: ceiling }
| Ownership::CoOwned { priority: ceiling }
| Ownership::Contended { ceiling }
if priority != ceiling =>
{
*ownership = Ownership::Contended {
ceiling: cmp::max(ceiling, priority),
};
if access.is_shared() {
sync_types.insert(res.ty.clone());
}
}
Ownership::Owned { priority: ceil } if ceil == priority => {
*ownership = Ownership::CoOwned { priority };
}
_ => {}
}
} else {
ownerships.insert(name.clone(), Ownership::Owned { priority });
}
}
}
// Create the list of used local resource Idents
let mut used_local_resource = IndexSet::new();
for (_, _, locals, _) in task_resources_list {
for (local, _) in locals {
used_local_resource.insert(local.clone());
}
}
// Most shared resources need to be `Send`, only 0 prio does not need it
let mut send_types = SendTypes::new();
for (name, res) in app.shared_resources.iter() {
if ownerships
.get(name)
.map(|ownership| match *ownership {
Ownership::Owned { priority: ceiling }
| Ownership::CoOwned { priority: ceiling }
| Ownership::Contended { ceiling } => ceiling != 0,
})
.unwrap_or(false)
{
send_types.insert(res.ty.clone());
}
}
// Most local resources need to be `Send` as well, only 0 prio does not need it
for (name, res) in app.local_resources.iter() {
if ownerships
.get(name)
.map(|ownership| match *ownership {
Ownership::Owned { priority: ceiling }
| Ownership::CoOwned { priority: ceiling }
| Ownership::Contended { ceiling } => ceiling != 0,
})
.unwrap_or(false)
{
send_types.insert(res.ty.clone());
}
}
let mut channels = Channels::new();
for (name, spawnee) in &app.software_tasks {
let spawnee_prio = spawnee.args.priority;
let channel = channels.entry(spawnee_prio).or_default();
channel.tasks.insert(name.clone());
// All inputs are send as we do not know from where they may be spawned.
spawnee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
}
// No channel should ever be empty
debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty()));
Ok(Analysis {
channels,
shared_resources: used_shared_resource,
local_resources: used_local_resource,
ownerships,
send_types,
sync_types,
})
}
// /// Priority ceiling
// pub type Ceiling = Option<u8>;
/// Task priority
pub type Priority = u8;
/// Resource name
pub type Resource = Ident;
/// Task name
pub type Task = Ident;
/// The result of analyzing an RTIC application
pub struct Analysis {
/// SPSC message channels
pub channels: Channels,
/// Shared resources
///
/// If a resource is not listed here it means that's a "dead" (never
/// accessed) resource and the backend should not generate code for it
pub shared_resources: UsedSharedResource,
/// Local resources
///
/// If a resource is not listed here it means that's a "dead" (never
/// accessed) resource and the backend should not generate code for it
pub local_resources: UsedLocalResource,
/// Resource ownership
pub ownerships: Ownerships,
/// These types must implement the `Send` trait
pub send_types: SendTypes,
/// These types must implement the `Sync` trait
pub sync_types: SyncTypes,
}
/// All channels, keyed by dispatch priority
pub type Channels = BTreeMap<Priority, Channel>;
/// Location of all *used* shared resources
pub type UsedSharedResource = IndexSet<Resource>;
/// Location of all *used* local resources
pub type UsedLocalResource = IndexSet<Resource>;
/// Resource ownership
pub type Ownerships = IndexMap<Resource, Ownership>;
/// These types must implement the `Send` trait
pub type SendTypes = Set<Box<Type>>;
/// These types must implement the `Sync` trait
pub type SyncTypes = Set<Box<Type>>;
/// A channel used to send messages
#[derive(Debug, Default)]
pub struct Channel {
/// Tasks that can be spawned on this channel
pub tasks: BTreeSet<Task>,
}
/// Resource ownership
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Ownership {
/// Owned by a single task
Owned {
/// Priority of the task that owns this resource
priority: u8,
},
/// "Co-owned" by more than one task; all of them have the same priority
CoOwned {
/// Priority of the tasks that co-own this resource
priority: u8,
},
/// Contended by more than one task; the tasks have different priorities
Contended {
/// Priority ceiling
ceiling: u8,
},
}
// impl Ownership {
// /// Whether this resource needs to a lock at this priority level
// pub fn needs_lock(&self, priority: u8) -> bool {
// match self {
// Ownership::Owned { .. } | Ownership::CoOwned { .. } => false,
//
// Ownership::Contended { ceiling } => {
// debug_assert!(*ceiling >= priority);
//
// priority < *ceiling
// }
// }
// }
//
// /// Whether this resource is exclusively owned
// pub fn is_owned(&self) -> bool {
// matches!(self, Ownership::Owned { .. })
// }
// }

View file

@ -0,0 +1,335 @@
//! Abstract Syntax Tree
use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type};
use crate::syntax::Map;
/// The `#[app]` attribute
#[derive(Debug)]
#[non_exhaustive]
pub struct App {
/// The arguments to the `#[app]` attribute
pub args: AppArgs,
/// The name of the `const` item on which the `#[app]` attribute has been placed
pub name: Ident,
/// The `#[init]` function
pub init: Init,
/// The `#[idle]` function
pub idle: Option<Idle>,
/// Resources shared between tasks defined in `#[shared]`
pub shared_resources: Map<SharedResource>,
/// Task local resources defined in `#[local]`
pub local_resources: Map<LocalResource>,
/// User imports
pub user_imports: Vec<ItemUse>,
/// User code
pub user_code: Vec<Item>,
/// Hardware tasks: `#[task(binds = ..)]`s
pub hardware_tasks: Map<HardwareTask>,
/// Async software tasks: `#[task]`
pub software_tasks: Map<SoftwareTask>,
}
/// Interrupts used to dispatch software tasks
pub type Dispatchers = Map<Dispatcher>;
/// Interrupt that could be used to dispatch software tasks
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Dispatcher {
/// Attributes that will apply to this interrupt handler
pub attrs: Vec<Attribute>,
}
/// The arguments of the `#[app]` attribute
#[derive(Debug)]
pub struct AppArgs {
/// Device
pub device: Path,
/// Peripherals
pub peripherals: bool,
/// Interrupts used to dispatch software tasks
pub dispatchers: Dispatchers,
}
/// The `init`-ialization function
#[derive(Debug)]
#[non_exhaustive]
pub struct Init {
/// `init` context metadata
pub args: InitArgs,
/// Attributes that will apply to this `init` function
pub attrs: Vec<Attribute>,
/// The name of the `#[init]` function
pub name: Ident,
/// The context argument
pub context: Box<Pat>,
/// The statements that make up this `init` function
pub stmts: Vec<Stmt>,
/// The name of the user provided shared resources struct
pub user_shared_struct: Ident,
/// The name of the user provided local resources struct
pub user_local_struct: Ident,
}
/// `init` context metadata
#[derive(Debug)]
#[non_exhaustive]
pub struct InitArgs {
/// Local resources that can be accessed from this context
pub local_resources: LocalResources,
}
impl Default for InitArgs {
fn default() -> Self {
Self {
local_resources: LocalResources::new(),
}
}
}
/// The `idle` context
#[derive(Debug)]
#[non_exhaustive]
pub struct Idle {
/// `idle` context metadata
pub args: IdleArgs,
/// Attributes that will apply to this `idle` function
pub attrs: Vec<Attribute>,
/// The name of the `#[idle]` function
pub name: Ident,
/// The context argument
pub context: Box<Pat>,
/// The statements that make up this `idle` function
pub stmts: Vec<Stmt>,
}
/// `idle` context metadata
#[derive(Debug)]
#[non_exhaustive]
pub struct IdleArgs {
/// Local resources that can be accessed from this context
pub local_resources: LocalResources,
/// Shared resources that can be accessed from this context
pub shared_resources: SharedResources,
}
impl Default for IdleArgs {
fn default() -> Self {
Self {
local_resources: LocalResources::new(),
shared_resources: SharedResources::new(),
}
}
}
/// Shared resource properties
#[derive(Debug)]
pub struct SharedResourceProperties {
/// A lock free (exclusive resource)
pub lock_free: bool,
}
/// A shared resource, defined in `#[shared]`
#[derive(Debug)]
#[non_exhaustive]
pub struct SharedResource {
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
pub cfgs: Vec<Attribute>,
/// `#[doc]` attributes like `/// this is a docstring`
pub docs: Vec<Attribute>,
/// Attributes that will apply to this resource
pub attrs: Vec<Attribute>,
/// The type of this resource
pub ty: Box<Type>,
/// Shared resource properties
pub properties: SharedResourceProperties,
}
/// A local resource, defined in `#[local]`
#[derive(Debug)]
#[non_exhaustive]
pub struct LocalResource {
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
pub cfgs: Vec<Attribute>,
/// `#[doc]` attributes like `/// this is a docstring`
pub docs: Vec<Attribute>,
/// Attributes that will apply to this resource
pub attrs: Vec<Attribute>,
/// The type of this resource
pub ty: Box<Type>,
}
/// An async software task
#[derive(Debug)]
#[non_exhaustive]
pub struct SoftwareTask {
/// Software task metadata
pub args: SoftwareTaskArgs,
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
pub cfgs: Vec<Attribute>,
/// Attributes that will apply to this interrupt handler
pub attrs: Vec<Attribute>,
/// The context argument
pub context: Box<Pat>,
/// The inputs of this software task
pub inputs: Vec<PatType>,
/// The statements that make up the task handler
pub stmts: Vec<Stmt>,
/// The task is declared externally
pub is_extern: bool,
}
/// Software task metadata
#[derive(Debug)]
#[non_exhaustive]
pub struct SoftwareTaskArgs {
/// The priority of this task
pub priority: u8,
/// Local resources that can be accessed from this context
pub local_resources: LocalResources,
/// Shared resources that can be accessed from this context
pub shared_resources: SharedResources,
}
impl Default for SoftwareTaskArgs {
fn default() -> Self {
Self {
priority: 1,
local_resources: LocalResources::new(),
shared_resources: SharedResources::new(),
}
}
}
/// A hardware task
#[derive(Debug)]
#[non_exhaustive]
pub struct HardwareTask {
/// Hardware task metadata
pub args: HardwareTaskArgs,
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
pub cfgs: Vec<Attribute>,
/// Attributes that will apply to this interrupt handler
pub attrs: Vec<Attribute>,
/// The context argument
pub context: Box<Pat>,
/// The statements that make up the task handler
pub stmts: Vec<Stmt>,
/// The task is declared externally
pub is_extern: bool,
}
/// Hardware task metadata
#[derive(Debug)]
#[non_exhaustive]
pub struct HardwareTaskArgs {
/// The interrupt or exception that this task is bound to
pub binds: Ident,
/// The priority of this task
pub priority: u8,
/// Local resources that can be accessed from this context
pub local_resources: LocalResources,
/// Shared resources that can be accessed from this context
pub shared_resources: SharedResources,
}
/// A `static mut` variable local to and owned by a context
#[derive(Debug)]
#[non_exhaustive]
pub struct Local {
/// Attributes like `#[link_section]`
pub attrs: Vec<Attribute>,
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
pub cfgs: Vec<Attribute>,
/// Type
pub ty: Box<Type>,
/// Initial value
pub expr: Box<Expr>,
}
/// A wrapper of the 2 kinds of locals that tasks can have
#[derive(Debug)]
#[non_exhaustive]
pub enum TaskLocal {
/// The local is declared externally (i.e. `#[local]` struct)
External,
/// The local is declared in the task
Declared(Local),
}
/// Resource access
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Access {
/// `[x]`, a mutable resource
Exclusive,
/// `[&x]`, a static non-mutable resource
Shared,
}
impl Access {
/// Is this enum in the `Exclusive` variant?
pub fn is_exclusive(&self) -> bool {
*self == Access::Exclusive
}
/// Is this enum in the `Shared` variant?
pub fn is_shared(&self) -> bool {
*self == Access::Shared
}
}
/// Shared resource access list in task attribute
pub type SharedResources = Map<Access>;
/// Local resource access/declaration list in task attribute
pub type LocalResources = Map<TaskLocal>;

View file

@ -0,0 +1,66 @@
use std::collections::HashSet;
use syn::parse;
use crate::syntax::ast::App;
pub fn app(app: &App) -> parse::Result<()> {
// Check that all referenced resources have been declared
// Check that resources are NOT `Exclusive`-ly shared
let mut owners = HashSet::new();
for (_, name, access) in app.shared_resource_accesses() {
if app.shared_resources.get(name).is_none() {
return Err(parse::Error::new(
name.span(),
"this shared resource has NOT been declared",
));
}
if access.is_exclusive() {
owners.insert(name);
}
}
for name in app.local_resource_accesses() {
if app.local_resources.get(name).is_none() {
return Err(parse::Error::new(
name.span(),
"this local resource has NOT been declared",
));
}
}
// Check that no resource has both types of access (`Exclusive` & `Shared`)
let exclusive_accesses = app
.shared_resource_accesses()
.filter_map(|(priority, name, access)| {
if priority.is_some() && access.is_exclusive() {
Some(name)
} else {
None
}
})
.collect::<HashSet<_>>();
for (_, name, access) in app.shared_resource_accesses() {
if access.is_shared() && exclusive_accesses.contains(name) {
return Err(parse::Error::new(
name.span(),
"this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`",
));
}
}
// check that dispatchers are not used as hardware tasks
for task in app.hardware_tasks.values() {
let binds = &task.args.binds;
if app.args.dispatchers.contains_key(binds) {
return Err(parse::Error::new(
binds.span(),
"dispatcher interrupts can't be used as hardware tasks",
));
}
}
Ok(())
}

View file

@ -0,0 +1,36 @@
use std::collections::{BTreeSet, HashMap};
use crate::syntax::ast::App;
pub fn app(app: &mut App, settings: &Settings) {
// "compress" priorities
// If the user specified, for example, task priorities of "1, 3, 6",
// compress them into "1, 2, 3" as to leave no gaps
if settings.optimize_priorities {
// all task priorities ordered in ascending order
let priorities = app
.hardware_tasks
.values()
.map(|task| Some(task.args.priority))
.chain(
app.software_tasks
.values()
.map(|task| Some(task.args.priority)),
)
.collect::<BTreeSet<_>>();
let map = priorities
.iter()
.cloned()
.zip(1..)
.collect::<HashMap<_, _>>();
for task in app.hardware_tasks.values_mut() {
task.args.priority = map[&Some(task.args.priority)];
}
for task in app.software_tasks.values_mut() {
task.args.priority = map[&Some(task.args.priority)];
}
}
}

View file

@ -0,0 +1,319 @@
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, parenthesized,
parse::{self, Parse, ParseStream, Parser},
token::Brace,
Ident, Item, LitInt, 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 {
_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) -> 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 priority = None;
let mut shared_resources = None;
let mut local_resources = None;
let mut 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();
// Handle equal sign
let _: Token![=] = content.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 = 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() {
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",
));
}
Either::Left(HardwareTaskArgs {
binds,
priority,
shared_resources,
local_resources,
})
} else {
Either::Right(SoftwareTaskArgs {
priority,
shared_resources,
local_resources,
})
})
})
.parse2(tokens)
}

View file

@ -0,0 +1,480 @@
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, Visibility,
};
use super::Input;
use crate::syntax::{
ast::{
App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs,
LocalResource, SharedResource, SoftwareTask,
},
parse::{self as syntax_parse, util},
Either, Map, Set,
};
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) -> 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 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 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(())
};
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)? {
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)? {
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());
}
_ => {
// 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,
shared_resources,
local_resources,
user_imports,
user_code,
hardware_tasks,
software_tasks,
})
}
}

View file

@ -0,0 +1,76 @@
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 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({name}::Context)`"),
))
}
}
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 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({name}::Context)`"),
))
}
}

View file

@ -0,0 +1,42 @@
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({name}::Context) -> !`"),
))
}
}

View file

@ -0,0 +1,51 @@
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)
{
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({name}::Context) -> (Shared resources struct, Local resources struct)`"
),
))
}
}

View file

@ -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()),
})
}
}

View file

@ -0,0 +1,76 @@
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)
&& item.sig.asyncness.is_some();
let span = item.sig.ident.span();
let name = item.sig.ident.to_string();
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,
});
}
}
Err(parse::Error::new(
span,
format!("this task handler must have type signature `async fn({name}::Context, ..)`"),
))
}
}
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)
&& item.sig.asyncness.is_some();
let span = item.sig.ident.span();
let name = item.sig.ident.to_string();
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,
});
}
}
Err(parse::Error::new(
span,
format!("this task handler must have type signature `async fn({name}::Context, ..)`"),
))
}
}

View file

@ -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) -> 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)
//
// We check the length and the last one here, analysis checks that the user
// provided structs are correct.
if t.elems.len() == 2 {
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
}
}