Skip to content

Commit

Permalink
feat(macros): add plugin macros to easi delcare a plugin.
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
vincenzopalazzo committed Jun 13, 2023
1 parent 6370b13 commit 60467c8
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 33 deletions.
45 changes: 19 additions & 26 deletions plugin_macros/examples/macros_ex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -28,31 +36,16 @@ fn on_rpc(plugin: &mut Plugin<State>, 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();
}
51 changes: 44 additions & 7 deletions plugin_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<State>, request: Value) -> Result<Value, PluginError> {
/// 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
Expand All @@ -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<State>, _request: Value) -> Result<Value, PluginError> {
/// /// 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<State>, request: Value) -> Result<Value, PluginError> {
/// Ok(json!({"is_dynamic": plugin.dynamic, "rpc_request": request}))
/// }
/// ```
#[proc_macro_attribute]
Expand Down
181 changes: 181 additions & 0 deletions plugin_macros/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
pub dynamic: Option<TokenTree>,
pub notificatios: Option<TokenStream>,
pub hooks: Option<TokenStream>,
pub rpc_methods: Option<TokenStream>,
}

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<L: KParserTracer>(
&self,
tokens: TokenStream,
tracer: &L,
) -> Result<PluginDeclaration, KParserError> {
trace!(tracer, "inputs stream: {tokens}");
let mut stream = KTokenStream::new(&tokens);
parse_stream(&mut stream, tracer)
}
}

fn parse_stream<T: KParserTracer>(
stream: &mut KTokenStream,
tracer: &T,
) -> Result<PluginDeclaration, KParserError> {
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<T: KParserTracer>(
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())))
}

0 comments on commit 60467c8

Please sign in to comment.