init project
This commit is contained in:
175
embassy-executor-macros/src/lib.rs
Normal file
175
embassy-executor-macros/src/lib.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod macros;
|
||||
mod util;
|
||||
use macros::*;
|
||||
|
||||
/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
|
||||
/// many concurrent tasks can be spawned (default is 1) for the function.
|
||||
///
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * The optional `pool_size` attribute must be 1 or greater.
|
||||
///
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// Declaring a task taking no arguments:
|
||||
///
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::task]
|
||||
/// async fn mytask() {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Declaring a task with a given pool size:
|
||||
///
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::task(pool_size = 4)]
|
||||
/// async fn mytask() {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
task::run(args.into(), item.into()).into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
main::run(args.into(), item.into(), &main::ARCH_AVR).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * Only a single `main` task may be declared.
|
||||
///
|
||||
/// ## Examples
|
||||
/// Spawning a task:
|
||||
///
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::main]
|
||||
/// async fn main(_s: embassy_executor::Spawner) {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning
|
||||
/// the corresponding function body as an async task.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * Only a single `main` task may be declared.
|
||||
///
|
||||
/// A user-defined entry macro must provided via the `entry` argument
|
||||
///
|
||||
/// ## Examples
|
||||
/// Spawning a task:
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::main(entry = "qingke_rt::entry")]
|
||||
/// async fn main(_s: embassy_executor::Spawner) {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
main::run(args.into(), item.into(), &main::ARCH_SPIN).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * Only a single `main` task may be declared.
|
||||
///
|
||||
/// A user-defined entry macro can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`.
|
||||
///
|
||||
/// ## Examples
|
||||
/// Spawning a task:
|
||||
///
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::main]
|
||||
/// async fn main(_s: embassy_executor::Spawner) {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Spawning a task using a custom entry macro:
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::main(entry = "esp_riscv_rt::entry")]
|
||||
/// async fn main(_s: embassy_executor::Spawner) {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
main::run(args.into(), item.into(), &main::ARCH_RISCV).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * Only a single `main` task may be declared.
|
||||
///
|
||||
/// ## Examples
|
||||
/// Spawning a task:
|
||||
///
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::main]
|
||||
/// async fn main(_s: embassy_executor::Spawner) {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
main::run(args.into(), item.into(), &main::ARCH_STD).into()
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * Only a single `main` task may be declared.
|
||||
///
|
||||
/// ## Examples
|
||||
/// Spawning a task:
|
||||
///
|
||||
/// ``` rust
|
||||
/// #[embassy_executor::main]
|
||||
/// async fn main(_s: embassy_executor::Spawner) {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
main::run(args.into(), item.into(), &main::ARCH_WASM).into()
|
||||
}
|
||||
184
embassy-executor-macros/src/macros/main.rs
Normal file
184
embassy-executor-macros/src/macros/main.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use darling::export::NestedMeta;
|
||||
use darling::FromMeta;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{ReturnType, Type};
|
||||
|
||||
use crate::util::*;
|
||||
|
||||
enum Flavor {
|
||||
Standard,
|
||||
Wasm,
|
||||
}
|
||||
|
||||
pub(crate) struct Arch {
|
||||
default_entry: Option<&'static str>,
|
||||
flavor: Flavor,
|
||||
}
|
||||
|
||||
pub static ARCH_AVR: Arch = Arch {
|
||||
default_entry: Some("avr_device::entry"),
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_RISCV: Arch = Arch {
|
||||
default_entry: Some("riscv_rt::entry"),
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_CORTEX_M: Arch = Arch {
|
||||
default_entry: Some("cortex_m_rt::entry"),
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_SPIN: Arch = Arch {
|
||||
default_entry: None,
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_STD: Arch = Arch {
|
||||
default_entry: None,
|
||||
flavor: Flavor::Standard,
|
||||
};
|
||||
|
||||
pub static ARCH_WASM: Arch = Arch {
|
||||
default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
|
||||
flavor: Flavor::Wasm,
|
||||
};
|
||||
|
||||
#[derive(Debug, FromMeta, Default)]
|
||||
struct Args {
|
||||
#[darling(default)]
|
||||
entry: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
|
||||
let mut errors = TokenStream::new();
|
||||
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let f: ItemFn = match syn::parse2(item.clone()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
let args = match NestedMeta::parse_meta_list(args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
let args = match Args::from_list(&args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
errors.extend(e.write_errors());
|
||||
Args::default()
|
||||
}
|
||||
};
|
||||
|
||||
let fargs = f.sig.inputs.clone();
|
||||
|
||||
if f.sig.asyncness.is_none() {
|
||||
error(&mut errors, &f.sig, "main function must be async");
|
||||
}
|
||||
if !f.sig.generics.params.is_empty() {
|
||||
error(&mut errors, &f.sig, "main function must not be generic");
|
||||
}
|
||||
if !f.sig.generics.where_clause.is_none() {
|
||||
error(&mut errors, &f.sig, "main function must not have `where` clauses");
|
||||
}
|
||||
if !f.sig.abi.is_none() {
|
||||
error(&mut errors, &f.sig, "main function must not have an ABI qualifier");
|
||||
}
|
||||
if !f.sig.variadic.is_none() {
|
||||
error(&mut errors, &f.sig, "main function must not be variadic");
|
||||
}
|
||||
match &f.sig.output {
|
||||
ReturnType::Default => {}
|
||||
ReturnType::Type(_, ty) => match &**ty {
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
||||
Type::Never(_) => {}
|
||||
_ => error(
|
||||
&mut errors,
|
||||
&f.sig,
|
||||
"main function must either not return a value, return `()` or return `!`",
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
if fargs.len() != 1 {
|
||||
error(&mut errors, &f.sig, "main function must have 1 argument: the spawner.");
|
||||
}
|
||||
|
||||
let entry = match args.entry.as_deref().or(arch.default_entry) {
|
||||
None => TokenStream::new(),
|
||||
Some(x) => match TokenStream::from_str(x) {
|
||||
Ok(x) => quote!(#[#x]),
|
||||
Err(e) => {
|
||||
error(&mut errors, &f.sig, e);
|
||||
TokenStream::new()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let f_body = f.body;
|
||||
let out = &f.sig.output;
|
||||
|
||||
let (main_ret, mut main_body) = match arch.flavor {
|
||||
Flavor::Standard => (
|
||||
quote!(!),
|
||||
quote! {
|
||||
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
|
||||
::core::mem::transmute(t)
|
||||
}
|
||||
|
||||
let mut executor = ::embassy_executor::Executor::new();
|
||||
let executor = unsafe { __make_static(&mut executor) };
|
||||
executor.run(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
})
|
||||
},
|
||||
),
|
||||
Flavor::Wasm => (
|
||||
quote!(Result<(), wasm_bindgen::JsValue>),
|
||||
quote! {
|
||||
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
|
||||
|
||||
executor.start(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
let mut main_attrs = TokenStream::new();
|
||||
for attr in f.attrs {
|
||||
main_attrs.extend(quote!(#attr));
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
main_body = quote! {loop{}};
|
||||
}
|
||||
|
||||
let result = quote! {
|
||||
#[::embassy_executor::task()]
|
||||
#[allow(clippy::future_not_send)]
|
||||
async fn __embassy_main(#fargs) #out {
|
||||
#f_body
|
||||
}
|
||||
|
||||
#entry
|
||||
#main_attrs
|
||||
fn main() -> #main_ret {
|
||||
#main_body
|
||||
}
|
||||
|
||||
#errors
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
2
embassy-executor-macros/src/macros/mod.rs
Normal file
2
embassy-executor-macros/src/macros/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod main;
|
||||
pub mod task;
|
||||
220
embassy-executor-macros/src/macros/task.rs
Normal file
220
embassy-executor-macros/src/macros/task.rs
Normal file
@@ -0,0 +1,220 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use darling::export::NestedMeta;
|
||||
use darling::FromMeta;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::visit::{self, Visit};
|
||||
use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type};
|
||||
|
||||
use crate::util::*;
|
||||
|
||||
#[derive(Debug, FromMeta, Default)]
|
||||
struct Args {
|
||||
#[darling(default)]
|
||||
pool_size: Option<syn::Expr>,
|
||||
/// Use this to override the `embassy_executor` crate path. Defaults to `::embassy_executor`.
|
||||
#[darling(default)]
|
||||
embassy_executor: Option<syn::Expr>,
|
||||
}
|
||||
|
||||
pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut errors = TokenStream::new();
|
||||
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let f: ItemFn = match syn::parse2(item.clone()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
let args = match NestedMeta::parse_meta_list(args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
let args = match Args::from_list(&args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
errors.extend(e.write_errors());
|
||||
Args::default()
|
||||
}
|
||||
};
|
||||
|
||||
let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
|
||||
attrs: vec![],
|
||||
lit: Lit::Int(LitInt::new("1", Span::call_site())),
|
||||
}));
|
||||
|
||||
let embassy_executor = args
|
||||
.embassy_executor
|
||||
.unwrap_or(Expr::Verbatim(TokenStream::from_str("::embassy_executor").unwrap()));
|
||||
|
||||
if f.sig.asyncness.is_none() {
|
||||
error(&mut errors, &f.sig, "task functions must be async");
|
||||
}
|
||||
if !f.sig.generics.params.is_empty() {
|
||||
error(&mut errors, &f.sig, "task functions must not be generic");
|
||||
}
|
||||
if !f.sig.generics.where_clause.is_none() {
|
||||
error(&mut errors, &f.sig, "task functions must not have `where` clauses");
|
||||
}
|
||||
if !f.sig.abi.is_none() {
|
||||
error(&mut errors, &f.sig, "task functions must not have an ABI qualifier");
|
||||
}
|
||||
if !f.sig.variadic.is_none() {
|
||||
error(&mut errors, &f.sig, "task functions must not be variadic");
|
||||
}
|
||||
match &f.sig.output {
|
||||
ReturnType::Default => {}
|
||||
ReturnType::Type(_, ty) => match &**ty {
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
||||
Type::Never(_) => {}
|
||||
_ => error(
|
||||
&mut errors,
|
||||
&f.sig,
|
||||
"task functions must either not return a value, return `()` or return `!`",
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
let mut args = Vec::new();
|
||||
let mut fargs = f.sig.inputs.clone();
|
||||
|
||||
for arg in fargs.iter_mut() {
|
||||
match arg {
|
||||
syn::FnArg::Receiver(_) => {
|
||||
error(&mut errors, arg, "task functions must not have `self` arguments");
|
||||
}
|
||||
syn::FnArg::Typed(t) => {
|
||||
check_arg_ty(&mut errors, &t.ty);
|
||||
match t.pat.as_mut() {
|
||||
syn::Pat::Ident(id) => {
|
||||
id.mutability = None;
|
||||
args.push((id.clone(), t.attrs.clone()));
|
||||
}
|
||||
_ => {
|
||||
error(
|
||||
&mut errors,
|
||||
arg,
|
||||
"pattern matching in task arguments is not yet supported",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let task_ident = f.sig.ident.clone();
|
||||
let task_inner_ident = format_ident!("__{}_task", task_ident);
|
||||
|
||||
let mut task_inner = f.clone();
|
||||
let visibility = task_inner.vis.clone();
|
||||
task_inner.vis = syn::Visibility::Inherited;
|
||||
task_inner.sig.ident = task_inner_ident.clone();
|
||||
|
||||
// assemble the original input arguments,
|
||||
// including any attributes that may have
|
||||
// been applied previously
|
||||
let mut full_args = Vec::new();
|
||||
for (arg, cfgs) in args {
|
||||
full_args.push(quote!(
|
||||
#(#cfgs)*
|
||||
#arg
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
let mut task_outer_body = quote! {
|
||||
trait _EmbassyInternalTaskTrait {
|
||||
type Fut: ::core::future::Future + 'static;
|
||||
fn construct(#fargs) -> Self::Fut;
|
||||
}
|
||||
|
||||
impl _EmbassyInternalTaskTrait for () {
|
||||
type Fut = impl core::future::Future + 'static;
|
||||
fn construct(#fargs) -> Self::Fut {
|
||||
#task_inner_ident(#(#full_args,)*)
|
||||
}
|
||||
}
|
||||
|
||||
const POOL_SIZE: usize = #pool_size;
|
||||
static POOL: #embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = #embassy_executor::raw::TaskPool::new();
|
||||
unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
|
||||
};
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
let mut task_outer_body = quote! {
|
||||
const POOL_SIZE: usize = #pool_size;
|
||||
static POOL: #embassy_executor::_export::TaskPoolRef = #embassy_executor::_export::TaskPoolRef::new();
|
||||
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
|
||||
};
|
||||
|
||||
let task_outer_attrs = task_inner.attrs.clone();
|
||||
|
||||
if !errors.is_empty() {
|
||||
task_outer_body = quote! {
|
||||
#![allow(unused_variables, unreachable_code)]
|
||||
let _x: #embassy_executor::SpawnToken<()> = ::core::todo!();
|
||||
_x
|
||||
};
|
||||
}
|
||||
|
||||
// Copy the generics + where clause to avoid more spurious errors.
|
||||
let generics = &f.sig.generics;
|
||||
let where_clause = &f.sig.generics.where_clause;
|
||||
|
||||
let result = quote! {
|
||||
// This is the user's task function, renamed.
|
||||
// We put it outside the #task_ident fn below, because otherwise
|
||||
// the items defined there (such as POOL) would be in scope
|
||||
// in the user's code.
|
||||
#[doc(hidden)]
|
||||
#task_inner
|
||||
|
||||
#(#task_outer_attrs)*
|
||||
#visibility fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken<impl Sized> #where_clause{
|
||||
#task_outer_body
|
||||
}
|
||||
|
||||
#errors
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
|
||||
struct Visitor<'a> {
|
||||
errors: &'a mut TokenStream,
|
||||
}
|
||||
|
||||
impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
|
||||
fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) {
|
||||
// only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`.
|
||||
if i.lifetime.is_none() {
|
||||
error(
|
||||
self.errors,
|
||||
i.and_token,
|
||||
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
|
||||
)
|
||||
}
|
||||
visit::visit_type_reference(self, i);
|
||||
}
|
||||
|
||||
fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
|
||||
if i.ident.to_string() != "static" {
|
||||
error(
|
||||
self.errors,
|
||||
i,
|
||||
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
|
||||
error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.");
|
||||
}
|
||||
}
|
||||
|
||||
Visit::visit_type(&mut Visitor { errors }, ty);
|
||||
}
|
||||
74
embassy-executor-macros/src/util.rs
Normal file
74
embassy-executor-macros/src/util.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility};
|
||||
|
||||
pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||
tokens.extend(error.into_compile_error());
|
||||
tokens
|
||||
}
|
||||
|
||||
pub fn error<A: ToTokens, T: Display>(s: &mut TokenStream, obj: A, msg: T) {
|
||||
s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error())
|
||||
}
|
||||
|
||||
/// Function signature and body.
|
||||
///
|
||||
/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of
|
||||
/// parsing it. This makes the macro not error if there's a syntax error in the body,
|
||||
/// which helps IDE autocomplete work better.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ItemFn {
|
||||
pub attrs: Vec<Attribute>,
|
||||
pub vis: Visibility,
|
||||
pub sig: Signature,
|
||||
pub brace_token: token::Brace,
|
||||
pub body: TokenStream,
|
||||
}
|
||||
|
||||
impl Parse for ItemFn {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut attrs = input.call(Attribute::parse_outer)?;
|
||||
let vis: Visibility = input.parse()?;
|
||||
let sig: Signature = input.parse()?;
|
||||
|
||||
let content;
|
||||
let brace_token = braced!(content in input);
|
||||
while content.peek(Token![#]) && content.peek2(Token![!]) {
|
||||
let content2;
|
||||
attrs.push(Attribute {
|
||||
pound_token: content.parse()?,
|
||||
style: AttrStyle::Inner(content.parse()?),
|
||||
bracket_token: bracketed!(content2 in content),
|
||||
meta: content2.parse()?,
|
||||
});
|
||||
}
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !content.is_empty() {
|
||||
body.push(content.parse::<TokenTree>()?);
|
||||
}
|
||||
let body = body.into_iter().collect();
|
||||
|
||||
Ok(ItemFn {
|
||||
attrs,
|
||||
vis,
|
||||
sig,
|
||||
brace_token,
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ItemFn {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer)));
|
||||
self.vis.to_tokens(tokens);
|
||||
self.sig.to_tokens(tokens);
|
||||
self.brace_token.surround(tokens, |tokens| {
|
||||
tokens.append_all(self.body.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user