1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
use proc_macro::TokenStream;
use proc_macro_error::{abort, proc_macro_error};
use quote::quote;
use syn::{parse_macro_input, ItemConst};
use anchor_codegen::{
enumeration::Enumeration,
generate::GenerateConfig,
output::Output,
reply::Reply,
static_string::{Shutdown, StaticString},
};
/// Sends a message to the remote end
///
/// This macro allows sending data to the remote end in a type safe way. The general syntax is:
/// ```
/// klipper_reply!(reply_name[, arg: type = value]);
/// ```
///
/// For example, the `uptime` command could be sent as:
/// ```
/// klippy_reply!(uptime, high: u32 = var_high, clock: u32 = var_clock);
/// ```
///
/// The `= value` part can be left out. In this case, a variable with the same name as the argument
/// from the local scope will be used. E.g. the following is valid:
/// ```
/// klipper_reply!(data, clock: u32 = clock.into(), data: u32);
/// ```
#[proc_macro_error]
#[proc_macro]
pub fn klipper_reply(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as Reply);
let sender = input.sender_fn_name();
let args = input
.args
.iter()
.map(|arg| match &arg.value {
Some(value) => quote! { #value },
None => {
let name = &arg.name;
quote! { #name }
}
})
.collect::<Vec<_>>();
TokenStream::from(quote! {
crate::_anchor_config::message_handlers::#sender(#(#args),*)
})
}
/// Sends a `printf`-style message to the remote end
///
/// Dynamic messages can be sent to the remote end using this command. The main use case is for
/// debugging. E.g.
/// ```
/// klipper_output!("This the %uth test! %*s?", 10, "A test string");
/// ```
///
/// A `printf`-style syntax is used, to match the syntax from Klipper. The format string is parsed
/// at compile time, including type checking. The supplied argument type must match the format
/// string.
///
/// The format strings available, and their matching types, are:
///
/// | Format string | Rust type |
/// |---------------|-----------|
/// | `%u` | `u32` |
/// | `%i` | `i32` |
/// | `%hu` | `u16` |
/// | `%hi` | `i16` |
/// | `%c` | `u8` |
/// | `%.*s` | `&[u8]` |
/// | `%*s` | `&str` |
#[proc_macro_error]
#[proc_macro]
pub fn klipper_output(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as Output);
let sender = input.sender_fn_name();
let args = input
.args
.iter()
.map(|arg| match &arg.value {
Some(value) => quote! { #value },
None => unreachable!(),
})
.collect::<Vec<_>>();
TokenStream::from(quote! {
crate::_anchor_config::message_handlers::#sender(#(#args),*)
})
}
/// Generate compile-time configuration
///
/// This macro generates the protocol dictionary, message handlers, encoders, and dispatcher
/// needed. The actual code is generated by the `anchor_codegen` build script, and the macro
/// ensures that the generated code is included correctly.
///
/// This must be called exactly once at the root of your crate, typically in `main.rs`.
///
/// The syntax is as follows:
/// ```
/// klipper_config_generate!(options);
/// ```
///
/// The following options are supported:
///
/// * `transport = path: type`
/// This is the `TransportOutput` that will be used when sending messages to the remote end.
/// The user crate must provide this, and it must be a global `const`. Any `klipper_reply!`
/// calls will call the `output` method. Both a path and type must be provided, both must be
/// fully expanded. E.g.:
/// `transport = crate::usb::TRANSPORT_OUTPUT: crate::usb::BufferTransportOutput`
///
/// * `context = type`
/// An optional context can be passed to all `klipper_command` functions. The lifetime `'ctx`
/// is available, and allows the context to capture the lifetime when the generated dispatcher
/// is called, and pass this along to the handler functions. If no context type is given, the
/// default is the empty tuple `()`.
///
/// An example invocation could be:
/// ```
/// klipper_config_generate!(
/// transport = crate::usb::TRANSPORT_OUTPUT: crate::usb::BufferTransportOutput,
/// context = &'ctx mut crate::State,
/// );
/// ```
///
/// This generates a module called `_anchor_config`, and exports a `KLIPPER_TRANSPORT` symbol from
/// it.
#[proc_macro_error]
#[proc_macro]
pub fn klipper_config_generate(item: TokenStream) -> TokenStream {
let cfg = parse_macro_input!(item as GenerateConfig);
if let Err(e) = cfg.validate() {
abort!("Invalid klipper config: {}", e);
}
let target = std::env::var("OUT_DIR").unwrap() + "/_anchor_config.rs";
TokenStream::from(quote! {
#[path = #target]
mod _anchor_config;
pub(crate) use _anchor_config::TRANSPORT as KLIPPER_TRANSPORT;
})
}
/// Adds a static string to the generated protocol dictionary
///
/// This directly adds a static string to the `static_string_id` dictionary. The returned value
/// will be the ID of the added string.
///
/// This compile to a constant value.
#[proc_macro_error]
#[proc_macro]
pub fn klipper_static_string(item: TokenStream) -> TokenStream {
let compile_name = parse_macro_input!(item as StaticString).compile_name();
TokenStream::from(quote! {
crate::_anchor_config::static_strings::#compile_name
})
}
/// Sends a `shutdown` message to the remote end
///
/// ```
/// klipper_shutdown!("Some error", cur_clock());
/// ```
///
/// When called, immediately sends a static string and clock to the remote end. It is up to the
/// user code to perform any further shutdown-related handling.
#[proc_macro_error]
#[proc_macro]
pub fn klipper_shutdown(item: TokenStream) -> TokenStream {
let info = parse_macro_input!(item as Shutdown);
let compile_name = info.msg.compile_name();
let clock = info.clock;
TokenStream::from(quote! {
crate::_anchor_config::message_handlers::send_reply_shutdown(
#clock,
crate::_anchor_config::static_strings::#compile_name
);
})
}
/// Creates a Klipper compatible enumeration
///
/// The general syntax can be seen by the following example:
/// ```
/// klipper_enumeration! {
/// #[derive(Debug)]
/// #[klipper_enumeration(name = "pin", rename_all = "UPPERCASE")]
/// enum Pins {
/// Range(PA, 0, 15),
/// Range(PB, 0, 15),
/// AdcTemperature,
/// }
/// }
/// ```
///
/// The item must always be an `enum`.
///
/// Variants can either be directly specified, or a range can be requested. For ranges, the format
/// is:
/// ```
/// Range(Prefix, start, count)
/// ```
/// This will generate `count` items named `Prefix{start+i}`.
///
/// Variants can be enabled or disabled using standard `#[cfg(feature...)]` feature flags.
///
/// The top item and the enumerations can accept parameters. These can be given using the
/// `#[klipper_enumeration(opts)]` attribute. For the top level item, `opts` can take the following
/// forms:
///
/// * `name = "a_name"`: An override name of the enumeration seen in the dictionary
/// * `rename_all = "UPPERCASE|lowercase|snake_case"`: a default renaming option for all variants
///
/// For each variant entry, the following options are available:
///
/// * `rename`: An override name of this variant in the dictionary. For range variants, all
/// values are renamed.
///
/// All values will be automatically mapped to IDs. Implementations of `TryFrom<u{8, 16, 32, 64,
/// size>` are generated automatically, along with implementations of `From<Self> for u{8, 16, 32,
/// 64, usize}`. The number of bits in these generated functions is determining by the number of
/// variants. E.g. if the enum has more than 255 variants, the `u8` functions are not generated.
#[proc_macro_error]
#[proc_macro]
pub fn klipper_enumeration(item: TokenStream) -> TokenStream {
let enumeration = parse_macro_input!(item as Enumeration);
TokenStream::from(enumeration.to_token_stream())
}
/// Creates protocol command handler
///
/// This attribute is put on functions that should be exposed to the remote end. Only free standing
/// functions are supported.
///
/// An example usage:
/// ```
/// #[klipper_command]
/// fn finalize_config(context: &mut State, crc: u32) {
/// ...
/// }
/// ```
///
/// Argumenst are automatically converted to protocol compatible types and from wire format back to
/// Rust values.
///
/// A context argument may optionally be included. If included, this argument **must** be called
/// `context` or `ctx` and **must** be the first argument. It must have a type matching the one
/// given as the `context` parameter to the `klipper_config_generate` macro.
///
/// The following types are supported: `u8`, `i16`, `u16`, `i32`, `u32`, `bool`, `&[u8]`.
///
/// While Anchor places no restrictions on the number of arguments, be aware that individual
/// messages in the protocol are limited to 64 bytes of length. For larger sized data, one must
/// split the data across multiple messages.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn klipper_command(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
/// Expose a constant
///
/// Rust constants can be exposed to the remote end by marking them as `#[klipper_constant]`. The
/// exposed constant must be either a string or an integer number.
///
/// ```
/// #[klipper_constant]
/// const CLOCK_FREQ: u32 = 100_000_000;
///
/// #[klipper_constant]
/// const MCU: &str = "beacon";
/// ```
#[proc_macro_error]
#[proc_macro_attribute]
pub fn klipper_constant(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemConst);
TokenStream::from(quote! {
#[allow(dead_code)]
#input
})
}