diff --git a/Cargo.toml b/Cargo.toml index 78d5de1..4dc9e2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_with = "3.11" thiserror = "2.0" +seify-macros = { path = "crates/seify-macros" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] once_cell = "1.20" diff --git a/crates/seify-macros/Cargo.lock b/crates/seify-macros/Cargo.lock new file mode 100644 index 0000000..939050b --- /dev/null +++ b/crates/seify-macros/Cargo.lock @@ -0,0 +1,101 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "seify-macros" +version = "0.1.0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/crates/seify-macros/Cargo.toml b/crates/seify-macros/Cargo.toml new file mode 100644 index 0000000..d06e651 --- /dev/null +++ b/crates/seify-macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "seify-macros" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +darling = "0.20.11" +proc-macro2 = "1.0.95" +quote = "1.0.40" +syn = "2.0.100" diff --git a/crates/seify-macros/src/lib.rs b/crates/seify-macros/src/lib.rs new file mode 100644 index 0000000..b615847 --- /dev/null +++ b/crates/seify-macros/src/lib.rs @@ -0,0 +1,17 @@ +extern crate proc_macro; +mod seify_drivers; +use seify_drivers::*; + +use proc_macro::TokenStream; +use proc_macro2::{TokenStream as TokenStream2, TokenTree}; +use quote::quote; +use syn::{Expr, ItemEnum, parse_macro_input}; + +/// Attribute macro to generate a `probe` method for enums annotated with `#[seify_drivers]`. +#[proc_macro_attribute] +pub fn seify_drivers(attr: TokenStream, item: TokenStream) -> TokenStream { + match seify_drivers_impl(attr.into(), item.into()) { + Ok(smth) => smth.into(), + Err(err) => err.into_compile_error().into(), + } +} diff --git a/crates/seify-macros/src/seify_drivers.rs b/crates/seify-macros/src/seify_drivers.rs new file mode 100644 index 0000000..0da4056 --- /dev/null +++ b/crates/seify-macros/src/seify_drivers.rs @@ -0,0 +1,144 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + Attribute, Expr, ExprArray, ExprPath, Ident, ItemEnum, ItemFn, Lit, Meta, MetaList, + MetaNameValue, Path, Variant, parse_macro_input, spanned::Spanned, +}; + +fn extract_probefn_ident(attr: &Expr) -> syn::Result { + // println!("Eonfg: {attr:#?}"); + if let Expr::Path(ExprPath { + path: Path { segments, .. }, + .. + }) = attr + { + if let Some(probefn_ident) = segments.get(0) { + Ok(probefn_ident.ident.clone()) + } else { + Err(syn::Error::new(attr.span(), "Invalid Expression #0002")) + } + } else { + Err(syn::Error::new(attr.span(), "Invalid Expression #0001")) + } +} + +fn extract_driver_names(attr: &Expr) -> syn::Result { + if let Expr::Array(expr_array) = attr { + Ok(expr_array.clone()) + } else { + Err(syn::Error::new(attr.span(), "Invalid Expression #0004")) + } +} + +#[derive(Debug, Default, Clone)] +struct DriverVariantProperties { + probefn_ident: Option, + driver_strs: Option, + driver_cfgs: Option, +} + +pub fn seify_drivers_impl(_attr: TokenStream, input: TokenStream) -> syn::Result { + // Parse the input enum + let mut input_enum = syn::parse2::(input)?; + let enum_ident = &input_enum.ident; + let variants = &mut input_enum.variants; + // let mut probefn_idents = Vec::new(); + // let mut driver_strs = Vec::new(); + // let mut driver_cfgs = Vec::new(); + + let mut driver_properties = Vec::new(); + + for variant in variants.iter_mut() { + let mut new_attrs = Vec::with_capacity(variant.attrs.len()); + + let mut driver_property = DriverVariantProperties::default(); + + for attr in &variant.attrs { + match &attr.meta { + Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("probefn") => { + // probefn_idents.push(extract_probefn_ident(value)?); + driver_property.probefn_ident = Some(extract_probefn_ident(value)?); + } + Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("names") => { + // driver_strs.push(extract_driver_names(value)?); + driver_property.driver_strs = Some(extract_driver_names(value)?); + } + Meta::List(MetaList { path, tokens, .. }) if path.is_ident("cfg") => { + // driver_cfgs.push(tokens.clone()); + driver_property.driver_cfgs = Some(tokens.clone()); + } + _ => new_attrs.push(attr.clone()), + } + } + + if driver_property.probefn_ident.is_none() { + return Err(syn::Error::new(variant.span(), "No probe function set.")); + } + + driver_properties.push(driver_property); + + // if !names_set { + // return Err(syn::Error::new(variant.span(), "No parsing names set")); + // } + + variant.attrs = new_attrs; + } + + let mut the_variants_matching = Vec::new(); + // println!("drivers properties: {:#?}", driver_properties); + for (idk, variant) in driver_properties.into_iter().zip(variants) { + let driver_cfg = idk.driver_cfgs.unwrap_or_default(); + let driver_probefn = idk.probefn_ident.unwrap(); + + let variant_ident = variant.ident.clone(); + + let smth = quote! { + #[cfg(#driver_cfg)] + { + if driver.is_none() || matches!(driver, Some(#enum_ident::#variant_ident)) { + devs.append(&mut #driver_probefn(&args)?); + } + } + #[cfg(not(#driver_cfg))] + { + if matches!(driver, Some(#enum_ident::#variant_ident)) { + return Err(Error::FeatureNotEnabled); + } + } + }; + + println!("smth: {:#?}", smth); + + the_variants_matching.push(smth); + } + + // Generate the impl block with the `probe` method + let expanded = quote! { + #input_enum + + impl #enum_ident { + + /// Calls each probe function in declaration order and returns concatenated results. + pub fn enumerate_with_args>(a: A) -> Result, crate::Error> { + + let args: Args = a.try_into().or(Err(Error::ValueError))?; + let mut devs = Vec::new(); + let driver = match args.get::("driver") { + Ok(s) => Some(s.parse::<#enum_ident>()?), + Err(_) => None, + }; + + #( + #the_variants_matching + )* + Ok(devs) + } + + fn from_driver_str(s: &str) -> Result { + todo!() + } + } + }; + + Ok(expanded.into()) +} diff --git a/src/dev_traits/mod.rs b/src/dev_traits/mod.rs new file mode 100644 index 0000000..b3b3be6 --- /dev/null +++ b/src/dev_traits/mod.rs @@ -0,0 +1,44 @@ +#![allow(clippy::empty_line_after_doc_comments)] + +// A device that can have simultaneous tx and rx streams +// eg BladeRf +trait FullDuplexDevice {} + +// A device that can have either a tx or rx streamer configured, but not at the same time +// eg HackRfOne +trait HalfDuplexDevice {} + +// A transmit only device +// eg Osmo-FL2K (I don't think there are any serious devices) +trait SimplexDeviceTx {} + +// A recieve only device +// eg RTL-SDR +trait SimplexDeviceRx {} + +// A device that is half duplex, but can rapidly switch between TX and RX (do any exist?) +// Not sure how fast things like the HackRfOne and AirSpy can switch. +trait TDDDevice {} + +// Any of the above devices will implement traits for any device. +// This just represents the trait that already exists in seify. +trait AnyDevice {} + +/// Channels +//////////////////////////// + +// Independent channels? +// Locked together channels? + +/// Streamers +//////////////////////////// + +// Streamers that have some fixed point datatype. +// will automatically implement the streamer trait. +trait FixedStreamerRx {} +trait FixedStreamerTx {} + +/// Other Features +/////////////////////////////////// + +trait AgcDevice {} diff --git a/src/lib.rs b/src/lib.rs index 89e0080..74e8222 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ pub use device::Device; pub use device::DeviceTrait; pub use device::GenericDevice; +pub mod dev_traits; + pub mod impls; mod range; @@ -13,6 +15,7 @@ pub use range::Range; pub use range::RangeItem; mod streamer; +use seify_macros::seify_drivers; pub use streamer::RxStreamer; pub use streamer::TxStreamer; @@ -67,45 +70,99 @@ impl From for Error { } } +fn aaronia_probefn(_args: &Args) -> Result, Error> { + todo!() +} + +fn aaronia_http_probefn(_args: &Args) -> Result, Error> { + todo!() +} + +fn dummy_probefn(_args: &Args) -> Result, Error> { + todo!() +} + +fn hackrf_probefn(_args: &Args) -> Result, Error> { + todo!() +} + +fn rtl_probefn(_args: &Args) -> Result, Error> { + todo!() +} + /// Supported hardware drivers. -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[seify_drivers] +#[derive(Debug, PartialEq)] #[non_exhaustive] -pub enum Driver { +enum Driver { + #[probefn = aaronia_probefn] + #[cfg(all(feature = "aaronia", any(target_os = "linux", target_os = "windows")))] Aaronia, + #[probefn = aaronia_http_probefn] + #[cfg(all( + feature = "aaronia_http", + any(target_os = "linux", target_os = "windows") + ))] AaroniaHttp, + #[probefn = dummy_probefn] + #[cfg(all(feature = "dummy", any(target_os = "linux", target_os = "windows")))] Dummy, + #[probefn = hackrf_probefn] + #[cfg(all(feature = "hackrfone", any(target_os = "linux", target_os = "windows")))] + #[names=["hackrf","hackrfone"]] HackRf, + #[probefn = rtl_probefn] + #[cfg(all(feature = "rtlsdr", any(target_os = "linux", target_os = "windows")))] + #[names = ["rtl", "rtl-sdr"]] RtlSdr, - Soapy, } impl FromStr for Driver { type Err = Error; - fn from_str(s: &str) -> Result { - let s = s.to_lowercase(); - if s == "aaronia" { - return Ok(Driver::Aaronia); - } - if s == "aaronia_http" || s == "aaronia-http" || s == "aaroniahttp" { - return Ok(Driver::AaroniaHttp); - } - if s == "rtlsdr" || s == "rtl-sdr" || s == "rtl" { - return Ok(Driver::RtlSdr); - } - if s == "soapy" || s == "soapysdr" { - return Ok(Driver::Soapy); - } - if s == "hackrf" || s == "hackrfone" { - return Ok(Driver::HackRf); - } - if s == "dummy" || s == "Dummy" { - return Ok(Driver::Dummy); - } - Err(Error::ValueError) + Driver::from_driver_str(s) } } +// /// Supported hardware drivers. +// #[derive(Debug, PartialEq, Serialize, Deserialize)] +// #[non_exhaustive] +// pub enum Driver { +// Aaronia, +// AaroniaHttp, +// Dummy, +// HackRf, +// RtlSdr, +// Soapy, +// } + +// impl FromStr for Driver { +// type Err = Error; + +// fn from_str(s: &str) -> Result { +// let s = s.to_lowercase(); +// if s == "aaronia" { +// return Ok(Driver::Aaronia); +// } +// if s == "aaronia_http" || s == "aaronia-http" || s == "aaroniahttp" { +// return Ok(Driver::AaroniaHttp); +// } +// if s == "rtlsdr" || s == "rtl-sdr" || s == "rtl" { +// return Ok(Driver::RtlSdr); +// } +// if s == "soapy" || s == "soapysdr" { +// return Ok(Driver::Soapy); +// } +// if s == "hackrf" || s == "hackrfone" { +// return Ok(Driver::HackRf); +// } +// if s == "dummy" || s == "Dummy" { +// return Ok(Driver::Dummy); +// } +// Err(Error::ValueError) +// } +// } + /// Direction (Rx/TX) #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum Direction { @@ -121,101 +178,101 @@ pub enum Direction { /// uniquely, i.e., passing the [`Args`] to [`Device::from_args`](crate::Device::from_args) will /// open this particular device. pub fn enumerate() -> Result, Error> { - enumerate_with_args(Args::new()) + Driver::enumerate_with_args(Args::new()) } -/// Enumerate devices with given [`Args`]. -/// -/// ## Returns -/// -/// A vector or [`Args`] that provide information about the device and can be used to identify it -/// uniquely, i.e., passing the [`Args`] to [`Device::from_args`](crate::Device::from_args) will -/// open this particular device. -pub fn enumerate_with_args>(a: A) -> Result, Error> { - let args: Args = a.try_into().or(Err(Error::ValueError))?; - let mut devs = Vec::new(); - let driver = match args.get::("driver") { - Ok(s) => Some(s.parse::()?), - Err(_) => None, - }; +// / Enumerate devices with given [`Args`]. +// / +// / ## Returns +// / +// / A vector or [`Args`] that provide information about the device and can be used to identify it +// / uniquely, i.e., passing the [`Args`] to [`Device::from_args`](crate::Device::from_args) will +// / open this particular device. +// pub fn enumerate_with_args>(a: A) -> Result, Error> { +// let args: Args = a.try_into().or(Err(Error::ValueError))?; +// let mut devs = Vec::new(); +// let driver = match args.get::("driver") { +// Ok(s) => Some(s.parse::()?), +// Err(_) => None, +// }; - #[cfg(all(feature = "aaronia", any(target_os = "linux", target_os = "windows")))] - { - if driver.is_none() || matches!(driver, Some(Driver::Aaronia)) { - devs.append(&mut impls::Aaronia::probe(&args)?) - } - } - #[cfg(not(all(feature = "aaronia", any(target_os = "linux", target_os = "windows"))))] - { - if matches!(driver, Some(Driver::Aaronia)) { - return Err(Error::FeatureNotEnabled); - } - } +// #[cfg(all(feature = "aaronia", any(target_os = "linux", target_os = "windows")))] +// { +// if driver.is_none() || matches!(driver, Some(Driver::Aaronia)) { +// devs.append(&mut impls::Aaronia::probe(&args)?) +// } +// } +// #[cfg(not(all(feature = "aaronia", any(target_os = "linux", target_os = "windows"))))] +// { +// if matches!(driver, Some(Driver::Aaronia)) { +// return Err(Error::FeatureNotEnabled); +// } +// } - #[cfg(all(feature = "aaronia_http", not(target_arch = "wasm32")))] - { - if driver.is_none() || matches!(driver, Some(Driver::AaroniaHttp)) { - devs.append(&mut impls::AaroniaHttp::probe(&args)?) - } - } - #[cfg(not(all(feature = "aaronia_http", not(target_arch = "wasm32"))))] - { - if matches!(driver, Some(Driver::AaroniaHttp)) { - return Err(Error::FeatureNotEnabled); - } - } +// #[cfg(all(feature = "aaronia_http", not(target_arch = "wasm32")))] +// { +// if driver.is_none() || matches!(driver, Some(Driver::AaroniaHttp)) { +// devs.append(&mut impls::AaroniaHttp::probe(&args)?) +// } +// } +// #[cfg(not(all(feature = "aaronia_http", not(target_arch = "wasm32"))))] +// { +// if matches!(driver, Some(Driver::AaroniaHttp)) { +// return Err(Error::FeatureNotEnabled); +// } +// } - #[cfg(all(feature = "rtlsdr", not(target_arch = "wasm32")))] - { - if driver.is_none() || matches!(driver, Some(Driver::RtlSdr)) { - devs.append(&mut impls::RtlSdr::probe(&args)?) - } - } - #[cfg(not(all(feature = "rtlsdr", not(target_arch = "wasm32"))))] - { - if matches!(driver, Some(Driver::RtlSdr)) { - return Err(Error::FeatureNotEnabled); - } - } +// #[cfg(all(feature = "rtlsdr", not(target_arch = "wasm32")))] +// { +// if driver.is_none() || matches!(driver, Some(Driver::RtlSdr)) { +// devs.append(&mut impls::RtlSdr::probe(&args)?) +// } +// } +// #[cfg(not(all(feature = "rtlsdr", not(target_arch = "wasm32"))))] +// { +// if matches!(driver, Some(Driver::RtlSdr)) { +// return Err(Error::FeatureNotEnabled); +// } +// } - #[cfg(all(feature = "soapy", not(target_arch = "wasm32")))] - { - if driver.is_none() || matches!(driver, Some(Driver::Soapy)) { - devs.append(&mut impls::Soapy::probe(&args)?) - } - } - #[cfg(not(all(feature = "soapy", not(target_arch = "wasm32"))))] - { - if matches!(driver, Some(Driver::Soapy)) { - return Err(Error::FeatureNotEnabled); - } - } +// #[cfg(all(feature = "soapy", not(target_arch = "wasm32")))] +// { +// if driver.is_none() || matches!(driver, Some(Driver::Soapy)) { +// devs.append(&mut impls::Soapy::probe(&args)?) +// } +// } +// #[cfg(not(all(feature = "soapy", not(target_arch = "wasm32"))))] +// { +// if matches!(driver, Some(Driver::Soapy)) { +// return Err(Error::FeatureNotEnabled); +// } +// } - #[cfg(all(feature = "hackrfone", not(target_arch = "wasm32")))] - { - if driver.is_none() || matches!(driver, Some(Driver::HackRf)) { - devs.append(&mut impls::HackRfOne::probe(&args)?) - } - } - #[cfg(not(all(feature = "hackrfone", not(target_arch = "wasm32"))))] - { - if matches!(driver, Some(Driver::HackRf)) { - return Err(Error::FeatureNotEnabled); - } - } - #[cfg(feature = "dummy")] - { - if driver.is_none() || matches!(driver, Some(Driver::Dummy)) { - devs.append(&mut impls::Dummy::probe(&args)?) - } - } - #[cfg(not(feature = "dummy"))] - { - if matches!(driver, Some(Driver::Dummy)) { - return Err(Error::FeatureNotEnabled); - } - } +// #[cfg(all(feature = "hackrfone", not(target_arch = "wasm32")))] +// { +// if driver.is_none() || matches!(driver, Some(Driver::HackRf)) { +// devs.append(&mut impls::HackRfOne::probe(&args)?) +// } +// } +// #[cfg(not(all(feature = "hackrfone", not(target_arch = "wasm32"))))] +// { +// if matches!(driver, Some(Driver::HackRf)) { +// return Err(Error::FeatureNotEnabled); +// } +// } +// #[cfg(feature = "dummy")] +// { +// if driver.is_none() || matches!(driver, Some(Driver::Dummy)) { +// devs.append(&mut impls::Dummy::probe(&args)?) +// } +// } +// #[cfg(not(feature = "dummy"))] +// { +// if matches!(driver, Some(Driver::Dummy)) { +// return Err(Error::FeatureNotEnabled); +// } +// } - let _ = &mut devs; - Ok(devs) -} +// let _ = &mut devs; +// Ok(devs) +// }