From 60467c84344ce3c9c6e1b82503f0bb993bc07817 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sun, 4 Jun 2023 21:02:54 +0200 Subject: [PATCH] feat(macros): add plugin macros to easi delcare a plugin. This commit implement a procedural macros that allow to decare a plugin with a declarative definition, inspired by the linux kernel modules in rust. An example of macros usage is the following code ``` fn main() { let plugin = plugin! { state: State::new(), dynamic: true, notification: [ on_rpc, ], methods: [ foo_rpc, ], hooks: [], }; plugin.start(); } ``` Signed-off-by: Vincenzo Palazzo --- plugin_macros/examples/macros_ex.rs | 45 +++---- plugin_macros/src/lib.rs | 51 ++++++-- plugin_macros/src/plugin.rs | 181 ++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 33 deletions(-) create mode 100644 plugin_macros/src/plugin.rs diff --git a/plugin_macros/examples/macros_ex.rs b/plugin_macros/examples/macros_ex.rs index 29574b3..0fd1692 100644 --- a/plugin_macros/examples/macros_ex.rs +++ b/plugin_macros/examples/macros_ex.rs @@ -12,6 +12,14 @@ use clightningrpc_plugin::plugin::Plugin; #[derive(Clone)] struct State; +// FIXME: implement a derive macros to register +// the option plugins +impl State { + pub fn new() -> Self { + Self + } +} + #[rpc_method( rpc_name = "foo_macro", description = "This is a simple and short description" @@ -28,31 +36,16 @@ fn on_rpc(plugin: &mut Plugin, request: &Value) { } fn main() { - // as fist step you need to make a new plugin instance - // more docs about Plugin struct is provided under the clightning_plugin crate - let mut plugin = Plugin::new(State, true); - - // FIXME: this is just for now, we will write a plugin macros - // that define the definition like in the linux kernel a module is - // defined. - // - // ``` - // module! { - // type: RustMinimal, - // name: "rust_minimal", - // author: "Rust for Linux Contributors", - // description: "Rust minimal sample", - // license: "GPL", - // } - // ``` - let call = on_rpc(); - plugin.register_notification(&call.on_event.clone(), call); - let call = foo_rpc(); - plugin.add_rpc_method( - &call.name.clone(), - &call.usage.clone(), - &call.description.clone(), - call, - ); + let plugin = plugin! { + state: State::new(), + dynamic: true, + notification: [ + on_rpc, + ], + methods: [ + foo_rpc, + ], + hooks: [], + }; plugin.start(); } diff --git a/plugin_macros/src/lib.rs b/plugin_macros/src/lib.rs index 829e07f..1eaac91 100644 --- a/plugin_macros/src/lib.rs +++ b/plugin_macros/src/lib.rs @@ -7,6 +7,7 @@ use kproc_parser::kparser::KParserTracer; use kproc_parser::proc_macro::TokenStream; mod notification; +mod plugin; mod rpc_method; mod attr_parser; @@ -18,6 +19,47 @@ impl KParserTracer for Tracer { eprintln!("\x1b[93mkproc-tracing\x1b[1;97m {msg}"); } } +/// procedural macros that can be used wit the following code +/// ```no_run +/// use serde_json::{json, Value}; +/// use clightningrpc_plugin_macros::{rpc_method, plugin}; +/// use clightningrpc_plugin::commands::RPCCommand; +/// use clightningrpc_plugin::plugin::Plugin; +/// use clightningrpc_plugin::errors::PluginError; +/// +/// #[derive(Clone)] +/// struct State; +/// +/// impl State { +/// pub fn new() -> Self { +/// Self +/// } +/// } +/// +/// #[rpc_method( +/// rpc_name = "foo", +/// description = "This is a simple and short description" +/// )] +/// pub fn foo_rpc(plugin: &mut Plugin, request: Value) -> Result { +/// Ok(json!({"is_dynamic": plugin.dynamic, "rpc_request": request})) +/// } +/// +/// fn main() { +/// let plugin = plugin! { +/// state: State::new(), +/// dynamic: true, +/// notification: [], +/// methods: [ +/// foo_rpc, +/// ], +/// }; +/// plugin.start(); +/// } +/// ``` +#[proc_macro] +pub fn plugin(attr: TokenStream) -> TokenStream { + plugin::parse(attr) +} /// procedural macros that can be used wit the following code /// ```no_run @@ -34,13 +76,8 @@ impl KParserTracer for Tracer { /// rpc_name = "foo", /// description = "This is a simple and short description" /// )] -/// pub fn foo_rpc(_plugin: &mut Plugin, _request: Value) -> Result { -/// /// The name of the parameters can be used only if used, otherwise can be omitted -/// /// the only rules that the macros require is to have a propriety with the following rules: -/// /// - Plugin as _plugin -/// /// - CLN JSON request as _request -/// /// The function parameter can be specified in any order. -/// Ok(json!({"is_dynamic": _plugin.dynamic, "rpc_request": _request})) +/// pub fn foo_rpc(plugin: &mut Plugin, request: Value) -> Result { +/// Ok(json!({"is_dynamic": plugin.dynamic, "rpc_request": request})) /// } /// ``` #[proc_macro_attribute] diff --git a/plugin_macros/src/plugin.rs b/plugin_macros/src/plugin.rs new file mode 100644 index 0000000..2fc3d51 --- /dev/null +++ b/plugin_macros/src/plugin.rs @@ -0,0 +1,181 @@ +//! Resolve the procedural macro +//! code to declare a new plugin. +use kproc_parser::kparser::{DummyTracer, KParserError, KParserTracer}; +use kproc_parser::kproc_macros::KTokenStream; +use kproc_parser::proc_macro::{TokenStream, TokenTree}; +use kproc_parser::{build_error, check, trace}; + +#[derive(Debug)] +pub struct PluginDeclaration { + pub state: Option, + pub dynamic: Option, + pub notificatios: Option, + pub hooks: Option, + pub rpc_methods: Option, +} + +impl std::fmt::Display for PluginDeclaration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let state = self.state.clone().unwrap_or("()".to_owned()); + writeln!(f, "{{")?; + writeln!( + f, + "let mut plugin = Plugin::new({}, {});", + state, + self.dynamic + .clone() + .map_or(String::from("false"), |val| val.to_string()) + )?; + if let Some(ref inner) = self.notificatios { + let mut inner = KTokenStream::new(&inner); + while !inner.is_end() { + let notification = inner.advance(); + writeln!(f, "let call = {}();", notification)?; + writeln!( + f, + "plugin.register_notification(&call.on_event.clone(), call);" + )?; + if let Err(err) = check!(",", inner.advance()) { + err.emit(); + return Ok(()); + } + } + } + if let Some(ref inner) = self.rpc_methods { + let mut inner = KTokenStream::new(&inner); + while !inner.is_end() { + let rpc = inner.advance(); + writeln!(f, "let call = {}();", rpc)?; + writeln!(f, "plugin.add_rpc_method(&call.name.clone(), &call.usage.clone(), &call.description.clone(), call);")?; + if let Err(err) = check!(",", inner.advance()) { + err.emit(); + return Ok(()); + } + } + } + + writeln!(f, "plugin\n }}") + } +} + +impl Default for PluginDeclaration { + fn default() -> Self { + Self { + state: None, + dynamic: None, + notificatios: None, + hooks: None, + rpc_methods: None, + } + } +} + +/// proc macro syntax is something like this +/// +/// ```ignore +/// let plugin = plugin! { +/// state: State::new(), +/// dynamic: true, +/// notifications: [ +/// on_rpc +/// ], +/// methods: [ +/// foo_rpc, +/// ], +/// hooks: [], +/// }; +/// plugin.start(); +/// ``` +pub(crate) fn parse(attr: TokenStream) -> TokenStream { + let tracer = DummyTracer {}; + let module = KModuleParser::new().parse(attr, &tracer); + if let Err(err) = module { + err.emit(); + panic!(); + } + let module = module.unwrap(); + trace!(tracer, "{module}"); + module.to_string().parse().unwrap() +} + +/// Module parser able to parse a proc macro syntax +/// inspired from the linux kernel module macro. +pub struct KModuleParser; + +impl KModuleParser { + pub fn new() -> Self { + KModuleParser + } + + pub fn parse( + &self, + tokens: TokenStream, + tracer: &L, + ) -> Result { + trace!(tracer, "inputs stream: {tokens}"); + let mut stream = KTokenStream::new(&tokens); + parse_stream(&mut stream, tracer) + } +} + +fn parse_stream( + stream: &mut KTokenStream, + tracer: &T, +) -> Result { + let mut dec = PluginDeclaration::default(); + while !stream.is_end() { + let (key, mut value) = parse_key_value(stream, tracer)?; + match key.to_string().as_str() { + "state" => { + let mut state = String::new(); + while !value.is_end() { + state += &value.advance().to_string(); + } + dec.state = Some(state); + } + "dynamic" => dec.dynamic = Some(value.advance()), + "notification" => { + let value = value.advance(); + let TokenTree::Group(inner) = value else { + return Err(build_error!(value, "should be an array!")); + }; + dec.notificatios = Some(inner.stream()); + } + "methods" => { + let value = value.advance(); + let TokenTree::Group(inner) = value else { + return Err(build_error!(value, "should be an array!")); + }; + dec.rpc_methods = Some(inner.stream()); + } + "hooks" => { + let value = value.advance(); + let TokenTree::Group(inner) = value else { + return Err(build_error!(value, "should be an array!")); + }; + dec.hooks = Some(inner.stream()); + } + _ => { + return Err(build_error!( + stream.peek().clone(), + "`{key}` not a plugin item!" + )) + } + } + } + Ok(dec) +} +fn parse_key_value( + stream: &mut KTokenStream, + _: &T, +) -> Result<(TokenTree, KTokenStream), KParserError> { + let key = stream.advance(); + check!(":", stream.advance())?; + let mut values = String::new(); + while !stream.match_tok(",") { + let value = stream.advance(); + values += &value.to_string(); + } + check!(",", stream.advance())?; + Ok((key, KTokenStream::new(&values.parse().unwrap()))) +}