Basic example to try the harmony! macro
This commit is contained in:
11
harmony_macros/Cargo.toml
Normal file
11
harmony_macros/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "harmony_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.42"
|
||||
syn = "2.0.109"
|
||||
118
harmony_macros/src/lib.rs
Normal file
118
harmony_macros/src/lib.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Expr, Ident, punctuated::Punctuated, token};
|
||||
|
||||
// Define the struct for our macro's input DSL
|
||||
struct HarmonyInput {
|
||||
inventory_expr: Expr,
|
||||
topology_expr: Expr,
|
||||
scores_array: Punctuated<Expr, token::Comma>,
|
||||
}
|
||||
|
||||
// Custom parser for `key: value,` syntax
|
||||
impl syn::parse::Parse for HarmonyInput {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut inventory_expr = None;
|
||||
let mut topology_expr = None;
|
||||
let mut scores_array = None;
|
||||
|
||||
while !input.is_empty() {
|
||||
let key: Ident = input.parse()?;
|
||||
input.parse::<token::Colon>()?;
|
||||
|
||||
if key == "inventory" {
|
||||
inventory_expr = Some(input.parse()?);
|
||||
} else if key == "topology" {
|
||||
topology_expr = Some(input.parse()?);
|
||||
} else if key == "scores" {
|
||||
let content;
|
||||
syn::bracketed!(content in input);
|
||||
scores_array = Some(Punctuated::parse_terminated(&content)?);
|
||||
} else {
|
||||
return Err(syn::Error::new(key.span(), "unknown key"));
|
||||
}
|
||||
// Eat the optional trailing comma
|
||||
let _ = input.parse::<token::Comma>();
|
||||
}
|
||||
|
||||
Ok(HarmonyInput {
|
||||
inventory_expr: inventory_expr.ok_or_else(|| input.error("missing 'inventory' key"))?,
|
||||
topology_expr: topology_expr.ok_or_else(|| input.error("missing 'topology' key"))?,
|
||||
scores_array: scores_array.ok_or_else(|| input.error("missing 'scores' key"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The main Harmony entrypoint macro.
|
||||
///
|
||||
/// This generates the `fn main()` and `tokio` runtime,
|
||||
/// and calls `harmony::run_cli` with your provided
|
||||
/// `inventory`, `topology`, and `scores`.
|
||||
#[proc_macro]
|
||||
pub fn harmony(input: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(input as HarmonyInput);
|
||||
|
||||
let inventory_expr = input.inventory_expr;
|
||||
let topology_expr = input.topology_expr;
|
||||
|
||||
// Create the `Box::new()` for each score
|
||||
let boxed_scores = input.scores_array.iter().map(|score_expr| {
|
||||
quote! { Box::new(#score_expr) }
|
||||
});
|
||||
|
||||
// Generate the `fn main()`
|
||||
let expanded = quote! {
|
||||
// This is the auto-generated main
|
||||
fn main() {
|
||||
// 1. Setup the Tokio runtime
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to build Tokio runtime");
|
||||
|
||||
runtime.block_on(async {
|
||||
// 3. Evaluate the user's expressions
|
||||
let inventory = #inventory_expr;
|
||||
let topology = #topology_expr;
|
||||
|
||||
// 4. This helper function is the key.
|
||||
// It lets the compiler infer `T` from `topology` and
|
||||
// type-check the `scores` against it.
|
||||
fn __harmony_collect_scores<T: harmony::Topology>(
|
||||
// `topology` is passed to pin down the type `T`
|
||||
_topology: &T,
|
||||
) -> Vec<Box<dyn harmony::Score<T>>> {
|
||||
let mut v: Vec<Box<dyn harmony::Score<T>>> = Vec::new();
|
||||
|
||||
#(
|
||||
v.push(#boxed_scores);
|
||||
)*
|
||||
|
||||
v
|
||||
}
|
||||
|
||||
// 5. Create the scores vector
|
||||
let scores_vec = {
|
||||
__harmony_collect_scores(&topology)
|
||||
};
|
||||
|
||||
// 6. Call the *real* run function
|
||||
let result = harmony::run_cli(
|
||||
inventory,
|
||||
topology,
|
||||
scores_vec,
|
||||
).await;
|
||||
|
||||
// 7. Handle the final result
|
||||
if let Err(e) = result {
|
||||
eprintln!("\nError: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
Reference in New Issue
Block a user