Skip to content

actor API syntax #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added

- CI changelog entry enforcer
- actor API syntax. for details see [RFC #52](https://github.com/rtic-rs/rfcs/pull/52)

### Changed

Expand Down
61 changes: 53 additions & 8 deletions src/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ use crate::{
Set,
};

type TaskName = String;

pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
// Collect all tasks into a vector
type TaskName = String;
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)> =
Expand Down Expand Up @@ -252,23 +252,51 @@ pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
let spawnee_prio = spawnee.args.priority;

let channel = channels.entry(spawnee_prio).or_default();
channel.tasks.insert(name.clone());
channel
.spawnees
.insert(Spawnee::Task { name: name.clone() });

// All inputs are now send as we do not know from where they may be spawned.
spawnee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
}

for (name, actor) in &app.actors {
let spawnee_prio = actor.priority;

if !actor.subscriptions.is_empty() {
for (index, subscription) in actor.subscriptions.iter().enumerate() {
let channel = channels.entry(spawnee_prio).or_default();
channel.spawnees.insert(Spawnee::Actor {
name: name.clone(),
subscription_index: index,
});

// All inputs are now send as we do not know from where they may be spawned.
send_types.insert(Box::new(subscription.ty.clone()));
}
}
}

// No channel should ever be empty
debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty()));
debug_assert!(channels
.values()
.all(|channel| !channel.spawnees.is_empty()));

// Compute channel capacities
for channel in channels.values_mut() {
channel.capacity = channel
.tasks
.spawnees
.iter()
.map(|name| app.software_tasks[name].args.capacity)
.map(|spawnee| match spawnee {
Spawnee::Task { name } => app.software_tasks[name].args.capacity,
Spawnee::Actor {
name,
subscription_index,
..
} => app.actors[name].subscriptions[*subscription_index].capacity,
})
.sum();
}

Expand Down Expand Up @@ -345,8 +373,25 @@ pub struct Channel {
/// The channel capacity
pub capacity: u8,

/// Tasks that can be spawned on this channel
pub tasks: BTreeSet<Task>,
/// Tasks / AO that can be spawned on this channel
pub spawnees: BTreeSet<Spawnee>,
}

/// What can be spawned
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum Spawnee {
/// A software task
Task {
/// The name of the task
name: Task,
},
/// An actor
Actor {
/// The name of the actor
name: Ident,
/// Index into the actor's `subscriptions` field
subscription_index: usize,
},
}

/// Resource ownership
Expand Down
31 changes: 31 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub struct App {
/// Task local resources defined in `#[local]`
pub local_resources: Map<LocalResource>,

/// Actors defined in the `#[actors]` struct
pub actors: Map<Actor>,

/// User imports
pub user_imports: Vec<ItemUse>,

Expand Down Expand Up @@ -90,6 +93,9 @@ pub struct Init {

/// The name of the user provided local resources struct
pub user_local_struct: Ident,

/// The name of the user provided actors struct
pub user_actors_struct: Option<Ident>,
}

/// `init` context metadata
Expand Down Expand Up @@ -192,6 +198,31 @@ pub struct LocalResource {
pub ty: Box<Type>,
}

/// An actor defined in the `#[actors]` struct.
#[derive(Debug)]
#[non_exhaustive]
pub struct Actor {
/// The priority of this actor
pub priority: u8,
/// The expression used to initialized this actor. If absent, uses late/runtime
/// initialization.
pub init: Option<Box<Expr>>,
/// Type of the actor.
pub ty: Box<Type>,
/// #[subscribe] attributes
pub subscriptions: Vec<Subscription>,
}

/// The `#[subscribe]` attribute of an actor
#[derive(Debug)]
#[non_exhaustive]
pub struct Subscription {
/// Capacity of this channel
pub capacity: u8,
/// Message type
pub ty: Type,
}

/// Monotonic
#[derive(Debug)]
#[non_exhaustive]
Expand Down
1 change: 1 addition & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod actor;
mod app;
mod hardware_task;
mod idle;
Expand Down
160 changes: 160 additions & 0 deletions src/parse/actor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use proc_macro2::{Ident, Span};
use syn::{
parenthesized,
parse::{self, Parse},
spanned::Spanned,
Field, LitInt, Token, Type, Visibility,
};

use crate::ast::{Actor, Subscription};

use super::util::{self, FilterAttrs};

impl Actor {
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, .. } = util::filter_attributes(item.attrs.clone());

if !cfgs.is_empty() {
return Err(parse::Error::new(span, "`#[cfg]` is not allowed on actors"));
}

let mut priority = None;
let mut init = None;
let mut subscriptions = Vec::new();

for attr in attrs {
match attr.path.get_ident() {
Some(name) => {
match &*name.to_string() {
"priority" => {
if priority.is_some() {
return Err(parse::Error::new(
attr.span(),
"only one `#[priority]` attribute is allowed on an actor",
));
}

let prio: EqPriority = syn::parse2(attr.tokens)?;
priority = Some(prio.priority);
}
"init" => {
if init.is_some() {
return Err(parse::Error::new(
attr.span(),
"only one `#[init]` attribute is allowed on an actor",
));
}

// `#[init(expr)]` can be parsed via `ExprParen`
let paren: syn::ExprParen = syn::parse2(attr.tokens)?;

init = Some(paren.expr);
}
"subscribe" => {
let subscribe: Subscribe = syn::parse2(attr.tokens)?;
let capacity = subscribe
.capacity
.map(|lit| {
lit.base10_digits().parse::<u8>().map_err(|_| {
parse::Error::new(lit.span(), "not a `u8` value")
})
})
.transpose()?;

subscriptions.push(Subscription {
ty: subscribe.ty,
capacity: capacity.unwrap_or(1),
});
}
_ => {
return Err(parse::Error::new(
name.span(),
"this attribute is not supported on actor declarations",
));
}
}
}
None => {
return Err(parse::Error::new(
attr.path.span(),
"this attribute is not supported on actor declarations",
));
}
}
}

Ok(Actor {
ty: Box::new(item.ty.clone()),
priority: priority.unwrap_or(1),
init,
subscriptions,
})
}
}

struct EqPriority {
priority: u8,
}

impl parse::Parse for EqPriority {
fn parse(input: parse::ParseStream<'_>) -> syn::Result<Self> {
let _eq: Token![=] = input.parse()?;
let lit: syn::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();
match value {
None | Some(0) => Err(parse::Error::new(
lit.span(),
"this literal must be in the range 1...255",
)),
Some(priority) => Ok(Self { priority }),
}
}
}

struct Subscribe {
ty: Type,
capacity: Option<LitInt>,
}

impl Parse for Subscribe {
fn parse(input: parse::ParseStream<'_>) -> syn::Result<Self> {
let content;
parenthesized!(content in input);

let ty = content.parse()?;

let capacity = if content.is_empty() {
None
} else {
let _: Token![,] = content.parse()?;
let ident: Ident = content.parse()?;

if ident == "capacity" {
let _: Token![=] = content.parse()?;
Some(content.parse()?)
} else {
return Err(parse::Error::new(
ident.span(),
format!("expected `capacity`, found `{}`", ident),
));
}
};

Ok(Self { ty, capacity })
}
}
Loading