From 2df381d33471a628073f2c4af4f7b47fb2f4c198 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 22 Jul 2026 00:34:30 +0200 Subject: [PATCH 1/6] Added daemon main file with separation of terminal --- Cargo.toml | 8 ++++++++ src/daemon/main.rs | 3 +++ src/{ => terminal}/command.rs | 0 src/{ => terminal}/formatting.rs | 0 src/{ => terminal}/main.rs | 0 src/{ => terminal}/types.rs | 0 6 files changed, 11 insertions(+) create mode 100644 src/daemon/main.rs rename src/{ => terminal}/command.rs (100%) rename src/{ => terminal}/formatting.rs (100%) rename src/{ => terminal}/main.rs (100%) rename src/{ => terminal}/types.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 4ea732d..c786396 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,11 @@ members = ["pulse-ui"] pulse-ui = { path = "pulse-ui", version = "0.1.0-alpha.0" } tokio = "1.52.3" crossterm = "0.29.0" + +[[bin]] +name = "pulse-trader" +path = "src/terminal/main.rs" + +[[bin]] +name = "pulse-trader-daemon" +path = "src/daemon/main.rs" diff --git a/src/daemon/main.rs b/src/daemon/main.rs new file mode 100644 index 0000000..bb34c15 --- /dev/null +++ b/src/daemon/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, World!") +} \ No newline at end of file diff --git a/src/command.rs b/src/terminal/command.rs similarity index 100% rename from src/command.rs rename to src/terminal/command.rs diff --git a/src/formatting.rs b/src/terminal/formatting.rs similarity index 100% rename from src/formatting.rs rename to src/terminal/formatting.rs diff --git a/src/main.rs b/src/terminal/main.rs similarity index 100% rename from src/main.rs rename to src/terminal/main.rs diff --git a/src/types.rs b/src/terminal/types.rs similarity index 100% rename from src/types.rs rename to src/terminal/types.rs From 5affe1adf078d857c143275a71027677ba28fb47 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 22 Jul 2026 00:40:27 +0200 Subject: [PATCH 2/6] Pulse terminal communication --- src/daemon/main.rs | 4 +++- src/daemon/ptc.rs | 1 + src/{terminal/types.rs => ptc.rs} | 0 src/terminal/command.rs | 4 ++-- src/terminal/formatting.rs | 2 +- src/terminal/main.rs | 30 +++++++++++++++--------------- src/terminal/ptc.rs | 1 + 7 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 src/daemon/ptc.rs rename src/{terminal/types.rs => ptc.rs} (100%) create mode 100644 src/terminal/ptc.rs diff --git a/src/daemon/main.rs b/src/daemon/main.rs index bb34c15..11cb70d 100644 --- a/src/daemon/main.rs +++ b/src/daemon/main.rs @@ -1,3 +1,5 @@ +pub mod ptc; + fn main() { println!("Hello, World!") -} \ No newline at end of file +} diff --git a/src/daemon/ptc.rs b/src/daemon/ptc.rs new file mode 100644 index 0000000..436f33c --- /dev/null +++ b/src/daemon/ptc.rs @@ -0,0 +1 @@ +include!("../ptc.rs"); diff --git a/src/terminal/types.rs b/src/ptc.rs similarity index 100% rename from src/terminal/types.rs rename to src/ptc.rs diff --git a/src/terminal/command.rs b/src/terminal/command.rs index 352f1a1..d0df560 100644 --- a/src/terminal/command.rs +++ b/src/terminal/command.rs @@ -1,4 +1,4 @@ -use crate::{PulseTradeApp, types::EventLog}; +use crate::{PulseTradeApp, ptc::EventLog}; impl PulseTradeApp { pub async fn execute_command(&mut self, ctx: &pulse_ui::state::Context, command: &str) { @@ -19,7 +19,7 @@ impl PulseTradeApp { _ => { self.logs.lock().await.push(EventLog { - kind: crate::types::LogKind::Err, + kind: crate::ptc::LogKind::Err, name: "cmd", message: format!("Command '{}' not found", command), }); diff --git a/src/terminal/formatting.rs b/src/terminal/formatting.rs index d8b1557..c316ed9 100644 --- a/src/terminal/formatting.rs +++ b/src/terminal/formatting.rs @@ -1,4 +1,4 @@ -use crate::types::{ +use crate::ptc::{ ActivePosition, Alert, AlertLevel, EventLog, InspectTarget, LogKind, MarketOverview, Signal, SignalKind, Status, WatchListItem, }; diff --git a/src/terminal/main.rs b/src/terminal/main.rs index 1fd204c..6fa640c 100644 --- a/src/terminal/main.rs +++ b/src/terminal/main.rs @@ -1,6 +1,6 @@ pub mod command; pub mod formatting; -pub mod types; +pub mod ptc; use std::any::Any; @@ -20,7 +20,7 @@ use pulse_ui::{ use crate::{ formatting::{Formatted, apply_padding}, - types::{ + ptc::{ ActivePosition, Alert, EventLog, InspectTarget, MarketOverview, Signal, Status, WatchListItem, }, @@ -88,30 +88,30 @@ impl App for PulseTradeApp { let mut signals = self.signals.lock().await; signals.push(Signal { - kind: types::SignalKind::Buy, + kind: ptc::SignalKind::Buy, symbol: "BTC".to_string(), - param: types::SignalParameter::Lim, + param: ptc::SignalParameter::Lim, price: 118_800.0, }); signals.push(Signal { - kind: types::SignalKind::Buy, + kind: ptc::SignalKind::Buy, symbol: "BTC".to_string(), - param: types::SignalParameter::Tap, + param: ptc::SignalParameter::Tap, price: 120_000.0, }); signals.push(Signal { - kind: types::SignalKind::Buy, + kind: ptc::SignalKind::Buy, symbol: "BTC".to_string(), - param: types::SignalParameter::Stl, + param: ptc::SignalParameter::Stl, price: 118_000.0, }); let mut logs = self.logs.lock().await; logs.push(EventLog { - kind: types::LogKind::Warn, + kind: ptc::LogKind::Warn, name: "pulse.init", message: "We're still not done yet ;)".to_string(), }); @@ -119,17 +119,17 @@ impl App for PulseTradeApp { let mut market_overview = self.market_overview.lock().await; market_overview.alerts.push(Alert { - level: types::AlertLevel::High, + level: ptc::AlertLevel::High, message: "BTC funding rate elevated".to_string(), }); market_overview.alerts.push(Alert { - level: types::AlertLevel::Medium, + level: ptc::AlertLevel::Medium, message: "Market volatility increasing".to_string(), }); market_overview.alerts.push(Alert { - level: types::AlertLevel::Low, + level: ptc::AlertLevel::Low, message: "ETH volatility returning to normal".to_string(), }); } @@ -282,13 +282,13 @@ async fn main() { logs: ctx.use_state(Vec::new()), inspect: ctx.use_state(InspectTarget::None), market_overview: ctx.use_state(MarketOverview { - trend: types::MarketTrend::Bullish, - volatility: types::Volatility::High, + trend: ptc::MarketTrend::Bullish, + volatility: ptc::Volatility::High, pressure: 0.324, alerts: Vec::new(), }), status: ctx.use_state(Status { - feed: types::Feed::Connected, + feed: ptc::Feed::Connected, exchange: "Binance".to_string(), dex: "DEX SCREENER".to_string(), latency: 18, diff --git a/src/terminal/ptc.rs b/src/terminal/ptc.rs new file mode 100644 index 0000000..436f33c --- /dev/null +++ b/src/terminal/ptc.rs @@ -0,0 +1 @@ +include!("../ptc.rs"); From 23a1949676e8502efa47536b90342f87532c2355 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 22 Jul 2026 14:17:07 +0200 Subject: [PATCH 3/6] Com protocol --- Cargo.lock | 10 ++ Cargo.toml | 4 +- pulse-macros/Cargo.toml | 12 ++ pulse-macros/src/lib.rs | 61 +++++++++ src/daemon/ptc.rs | 1 + src/pc.rs | 105 ++++++++++++++++ src/ptc.rs | 247 +++++++++++++++++++------------------ src/terminal/command.rs | 2 +- src/terminal/formatting.rs | 25 ++-- src/terminal/main.rs | 2 +- src/terminal/ptc.rs | 1 + 11 files changed, 329 insertions(+), 141 deletions(-) create mode 100644 pulse-macros/Cargo.toml create mode 100644 pulse-macros/src/lib.rs create mode 100644 src/pc.rs diff --git a/Cargo.lock b/Cargo.lock index 44c8c16..1f87829 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,12 +304,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pulse-macros" +version = "0.1.0-alpha.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pulse-trader" version = "0.1.0-alpha.0" dependencies = [ "chrono", "crossterm", + "pulse-macros", "pulse-ui", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index c786396..572e0f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,13 +6,15 @@ edition = "2024" [dependencies] chrono = "0.4.45" pulse-ui = { workspace = true } +pulse-macros = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } crossterm = { workspace = true } [workspace] -members = ["pulse-ui"] +members = ["pulse-macros", "pulse-ui"] [workspace.dependencies] +pulse-macros = { path = "pulse-macros", version = "0.1.0-alpha.0" } pulse-ui = { path = "pulse-ui", version = "0.1.0-alpha.0" } tokio = "1.52.3" crossterm = "0.29.0" diff --git a/pulse-macros/Cargo.toml b/pulse-macros/Cargo.toml new file mode 100644 index 0000000..882d11d --- /dev/null +++ b/pulse-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pulse-macros" +version = "0.1.0-alpha.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/pulse-macros/src/lib.rs b/pulse-macros/src/lib.rs new file mode 100644 index 0000000..cd62b37 --- /dev/null +++ b/pulse-macros/src/lib.rs @@ -0,0 +1,61 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Fields, ItemStruct, parse_macro_input}; + +#[proc_macro_attribute] +pub fn p_com(_: TokenStream, item: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(item as ItemStruct); + + let name = &input.ident; + + match &mut input.fields { + Fields::Named(fields) => { + for field in fields.named.iter_mut() { + field.vis = syn::Visibility::Public(syn::token::Pub::default()); + } + } + _ => { + return syn::Error::new_spanned(input, "p_com only supports structs with named fields") + .to_compile_error() + .into(); + } + } + + let fields = match &input.fields { + Fields::Named(fields) => &fields.named, + _ => { + return syn::Error::new_spanned(input, "p_com only supports structs with named fields") + .to_compile_error() + .into(); + } + }; + + let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); + let field_names2 = fields.iter().map(|f| f.ident.as_ref().unwrap()); + let field_types = fields.iter().map(|f| &f.ty); + + TokenStream::from(quote! { + #[derive(Debug, Clone)] + #input + + impl PulseCom for #name { + fn to_com(&self) -> Vec { + let mut vec = Vec::new(); + + #( + vec.extend(self.#field_names.to_com()); + )* + + vec + } + + fn from_com(com: &mut Vec) -> Self { + Self { + #( + #field_names2: <#field_types>::from_com(com), + )* + } + } + } + }) +} diff --git a/src/daemon/ptc.rs b/src/daemon/ptc.rs index 436f33c..ab16651 100644 --- a/src/daemon/ptc.rs +++ b/src/daemon/ptc.rs @@ -1 +1,2 @@ +include!("../pc.rs"); include!("../ptc.rs"); diff --git a/src/pc.rs b/src/pc.rs new file mode 100644 index 0000000..8c5cb31 --- /dev/null +++ b/src/pc.rs @@ -0,0 +1,105 @@ +pub trait PulseCom { + fn to_com(&self) -> Vec; + fn from_com(_com: &mut Vec) -> Self; +} + +impl PulseCom for Vec { + fn to_com(&self) -> Vec { + let mut vec = Vec::new(); + + vec.extend_from_slice(&(self.len() as u32).to_le_bytes()); + + for item in self { + vec.extend(item.to_com()); + } + + vec + } + + fn from_com(com: &mut Vec) -> Self { + let len_bytes: [u8; 4] = com.drain(..4).collect::>().try_into().unwrap(); + + let len = u32::from_le_bytes(len_bytes) as usize; + + let mut result = Vec::with_capacity(len); + + for _ in 0..len { + result.push(T::from_com(com)); + } + + result + } +} + +impl PulseCom for String { + fn to_com(&self) -> Vec { + let bytes = self.as_bytes(); + let mut out = Vec::with_capacity(4 + bytes.len()); + + out.extend_from_slice(&(bytes.len() as u32).to_le_bytes()); + out.extend_from_slice(bytes); + + out + } + + fn from_com(com: &mut Vec) -> Self { + let len = u32::from_le_bytes(com[..4].try_into().unwrap()) as usize; + com.drain(..4); + + let bytes: Vec = com.drain(..len).collect(); + String::from_utf8(bytes).unwrap() + } +} + +macro_rules! int_com { + ($t:ty) => { + impl PulseCom for $t { + fn to_com(&self) -> Vec { + self.to_le_bytes().to_vec() + } + + fn from_com(com: &mut Vec) -> Self { + const N: usize = std::mem::size_of::<$t>(); + let bytes: [u8; N] = com.drain(..N).collect::>().try_into().unwrap(); + <$t>::from_le_bytes(bytes) + } + } + }; +} + +int_com!(i8); +int_com!(i16); +int_com!(i32); +int_com!(i64); +int_com!(isize); + +int_com!(u8); +int_com!(u16); +int_com!(u32); +int_com!(u64); +int_com!(usize); + +int_com!(f64); +int_com!(f32); + +#[macro_export] +macro_rules! p_com { + (struct $name:ident { $($n:ident: $v:ty),* $(,)? }) => { + #[derive(Debug, Clone)] + pub struct $name { $(pub $n: $v),* } + + impl PulseCom for $name { + fn to_com(&self) -> Vec { + let mut vec = Vec::new(); + $(vec.extend(self.$n.to_com());)* + vec + } + + fn from_com(com: &mut Vec) -> Self { + Self { + $($n: <$v>::from_com(com),)* + } + } + } + }; +} diff --git a/src/ptc.rs b/src/ptc.rs index 2cde269..b25b6ee 100644 --- a/src/ptc.rs +++ b/src/ptc.rs @@ -1,24 +1,117 @@ -#[derive(Debug, Clone)] -pub struct WatchListItem { - pub symbol: String, - pub price: f64, - pub trend: f64, +use pulse_macros::p_com; + +#[p_com] +struct WatchListItem { + symbol: String, + price: f64, + trend: f64, } -#[derive(Debug, Clone)] -pub struct ActivePosition { - pub symbol: String, - pub profit: f64, - pub amount: f64, +#[p_com] +struct ActivePosition { + symbol: String, + profit: f64, + amount: f64, } -#[derive(Debug, Clone, Copy)] -pub enum MarketTrend { +#[p_com] +enum MarketTrend { Bullish, Bearish, Neutral, } +#[p_com] +enum Volatility { + Low, + Medium, + High, +} + +#[p_com] +struct MarketOverview { + trend: MarketTrend, + volatility: Volatility, + pressure: f64, + + alerts: Vec, +} + +#[p_com] +enum Feed { + Connected, + Disconnected, + Connecting, + Failed, +} + +#[p_com] +struct Status { + feed: Feed, + exchange: String, + dex: String, + latency: u16, +} + +#[p_com] +enum SignalKind { + Buy, + Sell, +} + +#[p_com] +enum SignalParameter { + Lim, + Stl, + Tap, + Chk, +} + +#[p_com] +struct Signal { + kind: SignalKind, + symbol: String, + param: SignalParameter, + price: f64, +} + +#[p_com] +enum LogKind { + Info, + Warn, + Err, + Debug, +} + +#[p_com] +struct EventLog { + kind: LogKind, + name: String, + message: String, +} + +#[p_com] +enum AlertLevel { + High, + Medium, + Low, +} + +#[p_com] +struct Alert { + level: AlertLevel, + message: String, +} + +#[p_com] +enum InspectTarget { + None, + Symbol(WatchListItem), + Position(ActivePosition), + Signal(Signal), + Alert(Alert), +} + impl std::fmt::Display for MarketTrend { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -29,11 +122,14 @@ impl std::fmt::Display for MarketTrend { } } -#[derive(Debug, Clone, Copy)] -pub enum Volatility { - Low, - Medium, - High, +impl std::fmt::Display for AlertLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::High => write!(f, "H"), + Self::Medium => write!(f, "M"), + Self::Low => write!(f, "L"), + } + } } impl std::fmt::Display for Volatility { @@ -46,80 +142,6 @@ impl std::fmt::Display for Volatility { } } -#[derive(Debug, Clone)] -pub struct MarketOverview { - pub trend: MarketTrend, - pub volatility: Volatility, - pub pressure: f64, - - pub alerts: Vec, -} - -#[derive(Debug, Clone, Copy)] -pub enum Feed { - Connected, - Disconnected, - Connecting, - Failed, -} - -pub struct Status { - pub feed: Feed, - pub exchange: String, - pub dex: String, - pub latency: u16, -} - -#[derive(Debug, Clone, Copy)] -pub enum SignalKind { - Buy, - Sell, -} - -impl std::fmt::Display for SignalKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Buy => write!(f, "BUY"), - Self::Sell => write!(f, "SELL"), - } - } -} - -#[derive(Debug, Clone, Copy)] -pub enum SignalParameter { - Lim, - Stl, - Tap, - Chk, -} - -impl std::fmt::Display for SignalParameter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Lim => write!(f, "LIM"), - Self::Stl => write!(f, "STL"), - Self::Tap => write!(f, "TAP"), - Self::Chk => write!(f, "CHK"), - } - } -} - -#[derive(Debug, Clone)] -pub struct Signal { - pub kind: SignalKind, - pub symbol: String, - pub param: SignalParameter, - pub price: f64, -} - -#[derive(Debug, Clone, Copy)] -pub enum LogKind { - Info, - Warn, - Err, - Debug, -} - impl std::fmt::Display for LogKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -131,41 +153,22 @@ impl std::fmt::Display for LogKind { } } -#[derive(Debug, Clone)] -pub struct EventLog { - pub kind: LogKind, - pub name: &'static str, - pub message: String, -} - -#[derive(Debug, Clone, Copy)] -pub enum AlertLevel { - High, - Medium, - Low, -} - -impl std::fmt::Display for AlertLevel { +impl std::fmt::Display for SignalKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::High => write!(f, "H"), - Self::Medium => write!(f, "M"), - Self::Low => write!(f, "L"), + Self::Buy => write!(f, "BUY"), + Self::Sell => write!(f, "SELL"), } } } -#[derive(Debug, Clone)] -pub struct Alert { - pub level: AlertLevel, - pub message: String, -} - -#[derive(Debug, Clone)] -pub enum InspectTarget { - None, - Symbol(Box<(WatchListItem, MarketOverview)>), - Position(ActivePosition), - Signal(Signal), - Alert(Alert), +impl std::fmt::Display for SignalParameter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Lim => write!(f, "LIM"), + Self::Stl => write!(f, "STL"), + Self::Tap => write!(f, "TAP"), + Self::Chk => write!(f, "CHK"), + } + } } diff --git a/src/terminal/command.rs b/src/terminal/command.rs index d0df560..8a8a4f6 100644 --- a/src/terminal/command.rs +++ b/src/terminal/command.rs @@ -20,7 +20,7 @@ impl PulseTradeApp { _ => { self.logs.lock().await.push(EventLog { kind: crate::ptc::LogKind::Err, - name: "cmd", + name: "cmd".to_string(), message: format!("Command '{}' not found", command), }); } diff --git a/src/terminal/formatting.rs b/src/terminal/formatting.rs index c316ed9..9b62766 100644 --- a/src/terminal/formatting.rs +++ b/src/terminal/formatting.rs @@ -12,22 +12,15 @@ impl Formatted for InspectTarget { match self { Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()], - Self::Symbol(boxed) => { - let (watch, overview) = &**boxed; - vec![ - Property("Symbol", format!("\x1b[35m{}\x1b[0m", watch.symbol)), - Property("Price", format!("\x1b[96m{}\x1b[0m", format_f64(watch.price))), - Property("Trend", format!("{}", format_f64(watch.trend))), - Property("Market", format!("{}", overview.trend)), - Property("Volatility", format!("{}", overview.volatility)), - Property( - "Pressure", - format!("{:+.2}%", overview.pressure * 100.0), - ), - Property("Alerts", format!("{}", overview.alerts.len())), - ] - .get_formatted() - } + Self::Symbol(watch) => vec![ + Property("Symbol", format!("\x1b[35m{}\x1b[0m", watch.symbol)), + Property( + "Price", + format!("\x1b[96m{}\x1b[0m", format_f64(watch.price)), + ), + Property("Trend", format!("{}", format_f64(watch.trend))), + ] + .get_formatted(), Self::Position(pos) => vec![ Property("Symbol", format!("\x1b[35m{}\x1b[0m", pos.symbol)), diff --git a/src/terminal/main.rs b/src/terminal/main.rs index 6fa640c..1b90a56 100644 --- a/src/terminal/main.rs +++ b/src/terminal/main.rs @@ -112,7 +112,7 @@ impl App for PulseTradeApp { logs.push(EventLog { kind: ptc::LogKind::Warn, - name: "pulse.init", + name: "pulse.init".to_string(), message: "We're still not done yet ;)".to_string(), }); diff --git a/src/terminal/ptc.rs b/src/terminal/ptc.rs index 436f33c..ab16651 100644 --- a/src/terminal/ptc.rs +++ b/src/terminal/ptc.rs @@ -1 +1,2 @@ +include!("../pc.rs"); include!("../ptc.rs"); From c6aa95b2bb77438bf67d4fdb48cbb386d34b6358 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 22 Jul 2026 14:22:19 +0200 Subject: [PATCH 4/6] Com protocol --- pulse-macros/src/lib.rs | 117 +++++++++++++++++++++++++++++++++++++++- src/ptc.rs | 30 +++++------ 2 files changed, 130 insertions(+), 17 deletions(-) diff --git a/pulse-macros/src/lib.rs b/pulse-macros/src/lib.rs index cd62b37..e852c81 100644 --- a/pulse-macros/src/lib.rs +++ b/pulse-macros/src/lib.rs @@ -1,11 +1,23 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{Fields, ItemStruct, parse_macro_input}; +use syn::{Fields, ItemEnum, ItemStruct, parse_macro_input}; #[proc_macro_attribute] pub fn p_com(_: TokenStream, item: TokenStream) -> TokenStream { - let mut input = parse_macro_input!(item as ItemStruct); + let input = parse_macro_input!(item as syn::Item); + match input { + syn::Item::Struct(s) => expand_struct(s), + syn::Item::Enum(e) => expand_enum(e), + _ => { + return syn::Error::new_spanned(input, "p_com only supports structs and enums") + .to_compile_error() + .into(); + } + } +} + +fn expand_struct(mut input: ItemStruct) -> TokenStream { let name = &input.ident; match &mut input.fields { @@ -59,3 +71,104 @@ pub fn p_com(_: TokenStream, item: TokenStream) -> TokenStream { } }) } + +fn expand_enum(input: ItemEnum) -> TokenStream { + let name = &input.ident; + + let to_com = input.variants.iter().enumerate().map(|(i, variant)| { + let ident = &variant.ident; + let tag = i as u8; + + match &variant.fields { + Fields::Unit => quote! { + Self::#ident => { + vec.push(#tag); + } + }, + + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! { + Self::#ident(v) => { + vec.push(#tag); + vec.extend(v.to_com()); + } + }, + + Fields::Named(fields) => { + let names = fields.named.iter().map(|f| f.ident.as_ref().unwrap()); + + let names2 = fields.named.iter().map(|f| f.ident.as_ref().unwrap()); + + quote! { + Self::#ident { #( #names ),* } => { + vec.push(#tag); + #( vec.extend(#names2.to_com()); )* + } + } + } + + _ => { + panic!("tuple variants with >1 field are not supported"); + } + } + }); + + let from_com = input.variants.iter().enumerate().map(|(i, variant)| { + let ident = &variant.ident; + let tag = i as u8; + + match &variant.fields { + Fields::Unit => quote! { + #tag => Self::#ident, + }, + + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + let ty = &fields.unnamed.first().unwrap().ty; + + quote! { + #tag => Self::#ident(<#ty>::from_com(com)), + } + } + + Fields::Named(fields) => { + let names = fields.named.iter().map(|f| f.ident.as_ref().unwrap()); + let tys = fields.named.iter().map(|f| &f.ty); + + quote! { + #tag => Self::#ident { + #( + #names: <#tys>::from_com(com), + )* + }, + } + } + + _ => panic!("tuple variants with >1 field are not supported"), + } + }); + + TokenStream::from(quote! { + #[derive(Debug, Clone)] + #input + + impl PulseCom for #name { + fn to_com(&self) -> Vec { + let mut vec = Vec::new(); + + match self { + #( #to_com )* + } + + vec + } + + fn from_com(com: &mut Vec) -> Self { + let kind = com.remove(0); + + match kind { + #( #from_com )* + _ => panic!("invalid {} discriminant {}", stringify!(#name), kind), + } + } + } + }) +} diff --git a/src/ptc.rs b/src/ptc.rs index b25b6ee..4dc117c 100644 --- a/src/ptc.rs +++ b/src/ptc.rs @@ -1,35 +1,35 @@ use pulse_macros::p_com; #[p_com] -struct WatchListItem { +pub struct WatchListItem { symbol: String, price: f64, trend: f64, } #[p_com] -struct ActivePosition { +pub struct ActivePosition { symbol: String, profit: f64, amount: f64, } #[p_com] -enum MarketTrend { +pub enum MarketTrend { Bullish, Bearish, Neutral, } #[p_com] -enum Volatility { +pub enum Volatility { Low, Medium, High, } #[p_com] -struct MarketOverview { +pub struct MarketOverview { trend: MarketTrend, volatility: Volatility, pressure: f64, @@ -38,7 +38,7 @@ struct MarketOverview { } #[p_com] -enum Feed { +pub enum Feed { Connected, Disconnected, Connecting, @@ -46,7 +46,7 @@ enum Feed { } #[p_com] -struct Status { +pub struct Status { feed: Feed, exchange: String, dex: String, @@ -54,13 +54,13 @@ struct Status { } #[p_com] -enum SignalKind { +pub enum SignalKind { Buy, Sell, } #[p_com] -enum SignalParameter { +pub enum SignalParameter { Lim, Stl, Tap, @@ -68,7 +68,7 @@ enum SignalParameter { } #[p_com] -struct Signal { +pub struct Signal { kind: SignalKind, symbol: String, param: SignalParameter, @@ -76,7 +76,7 @@ struct Signal { } #[p_com] -enum LogKind { +pub enum LogKind { Info, Warn, Err, @@ -84,27 +84,27 @@ enum LogKind { } #[p_com] -struct EventLog { +pub struct EventLog { kind: LogKind, name: String, message: String, } #[p_com] -enum AlertLevel { +pub enum AlertLevel { High, Medium, Low, } #[p_com] -struct Alert { +pub struct Alert { level: AlertLevel, message: String, } #[p_com] -enum InspectTarget { +pub enum InspectTarget { None, Symbol(WatchListItem), Position(ActivePosition), From ce1ebc74b12d0271410208410d55cba857d388e1 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 22 Jul 2026 14:34:16 +0200 Subject: [PATCH 5/6] Working protocol --- src/daemon/main.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/daemon/main.rs b/src/daemon/main.rs index 11cb70d..61b7dd0 100644 --- a/src/daemon/main.rs +++ b/src/daemon/main.rs @@ -1,5 +1,22 @@ pub mod ptc; +use std::time::Instant; + +use ptc::PulseCom; + fn main() { - println!("Hello, World!") + let input = ptc::EventLog { + kind: ptc::LogKind::Warn, + name: "Test".to_string(), + message: "Hello, WOrld".to_string(), + }; + + let start = Instant::now(); + + let mut val = input.to_com(); + let out = ptc::EventLog::from_com(&mut val); + + let elapsed = start.elapsed(); + + println!("{:?} {:?}", out, elapsed); } From a020484af2deda199bfa9cab27d2fc0a7aa9b69a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 22 Jul 2026 14:41:25 +0200 Subject: [PATCH 6/6] Improved from_com --- pulse-macros/src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pulse-macros/src/lib.rs b/pulse-macros/src/lib.rs index e852c81..d554b2d 100644 --- a/pulse-macros/src/lib.rs +++ b/pulse-macros/src/lib.rs @@ -44,7 +44,6 @@ fn expand_struct(mut input: ItemStruct) -> TokenStream { let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_names2 = fields.iter().map(|f| f.ident.as_ref().unwrap()); - let field_types = fields.iter().map(|f| &f.ty); TokenStream::from(quote! { #[derive(Debug, Clone)] @@ -64,7 +63,7 @@ fn expand_struct(mut input: ItemStruct) -> TokenStream { fn from_com(com: &mut Vec) -> Self { Self { #( - #field_names2: <#field_types>::from_com(com), + #field_names2: PulseCom::from_com(com), )* } } @@ -122,21 +121,18 @@ fn expand_enum(input: ItemEnum) -> TokenStream { }, Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - let ty = &fields.unnamed.first().unwrap().ty; - quote! { - #tag => Self::#ident(<#ty>::from_com(com)), + #tag => Self::#ident(PulseCom::from_com(com)), } } Fields::Named(fields) => { let names = fields.named.iter().map(|f| f.ident.as_ref().unwrap()); - let tys = fields.named.iter().map(|f| &f.ty); quote! { #tag => Self::#ident { #( - #names: <#tys>::from_com(com), + #names: PulseCom::from_com(com), )* }, }