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
// devela_macros
//
//!
//! ## Conditional compilation
//!
//! Each form of conditional compilation takes a compilation predicate that
//! evaluates to `true` or `false`.
//!
//! These are the [`#[compile]`][compile()] and [`#[compile_attr]`][compile_attr()]
//! attributes and the [`cif!`][cif()] macro.
//!
//! They are similar to the [`#[cfg]`][1] and [`#[cfg_attr]`][2] attributes and
//! the [`cfg!`][3] macro, except they use *compilation predicates*.
//!
//! ### Compilation predicates
//!
//! The following compilation predicates are supported:
//!
//! - unary:
//! - A bare predicate returns `true` only if it is the **`true`** literal
//! - `not()`: returns `true` only if the predicate does **not** evaluate to **`true`**.
//!
//! - binary:
//! - `eq()`: returns `true` if both predicates are evaluated as **equal**.
//! - `ne()`: returns `true` if both predicates are **not** evaluated as **equal**.
//! - `xor()`: returns `true` if **only one** predicate **is `true`**, but **not both**.
//! - `ge()`: returns `true` if both predicates are **numbers** and the first **>=** the second.
//! - `gt()`: returns `true` if both predicates are **numbers** and the first **>** the second.
//! - `le()`: returns `true` if both predicates are **numbers** and the first **<=** the second.
//! - `lt()`: returns `true` if both predicates are **numbers** and the first **<** the second.
//!
//! - non-binary:
//! - `any()`: returns `true` if **any** predicate **is `true`**.
//! - `all()`: returns `true` if **all** predicates **are `true`**.
//! - `none()`: returns `true` if there is **no given** predicate.
//! - `some()`: returns `true` if there is **some given** predicate.
//! - `diff()`: returns `true` if **any** predicate has a **different text**.
//! - `same()`: returns `true` if **all** the predicates have the **same text**.
//! - `xany()`: returns `true` if there are **any `true`** predicates, but **not all**.
//! - `xodd()`: returns `true` if there is an **odd number** of `true` predicates.
//! - `xone()`: returns `true` if there is just **one `true`** predicate, but **no more**.
//!
//! When more than 1 predicate is supported, they are separated by commas.
//!
//! [1]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute
//! [2]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute
//! [3]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-macro
//
// warnings
#![warn(clippy::all)]
// environment
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "safe", forbid(unsafe_code))]
#![cfg_attr(feature = "nightly", feature(doc_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{format, string::ToString};
extern crate proc_macro;
use proc_macro::TokenStream;
#[cfg(feature = "alloc")]
#[cfg(test)]
mod tests;
mod common;
#[cfg(feature = "alloc")]
use common::{compile_eval, split_args};
/// Conditionally compiles the thing it is attached to based on the
/// [compilation predicate][crate#compilation-predicates].
///
/// # Examples
/// ```
#[doc = include_str!("../examples/compile.rs")]
/// ```
#[proc_macro_attribute]
#[cfg(feature = "alloc")]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "alloc")))]
pub fn compile(argument: TokenStream, input: TokenStream) -> TokenStream {
if compile_eval(argument.to_string()) {
input
} else {
proc_macro::TokenStream::new()
}
}
/// Conditionally compiles the given attributes based on the
/// [compilation predicate][crate#compilation-predicates].
///
/// # Examples
/// ```
#[doc = include_str!("../examples/compile_attr.rs")]
/// ```
#[proc_macro_attribute]
#[cfg(feature = "alloc")]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "alloc")))]
pub fn compile_attr(args: TokenStream, input: TokenStream) -> TokenStream {
let args = args.to_string();
let mut args = split_args(&args);
if args.is_empty() {
panic!("The compile_attr macro requires at least one argument");
}
let condition = args.remove(0);
if compile_eval(condition) {
let mut expanded = input.to_string();
for attribute in args {
expanded = format!("#[{}] {}", attribute, expanded);
}
expanded.parse().expect("Couldn't parse as a TokenStream")
} else {
input
}
}
/// Evaluates to either a `true` of `false` literal based on the
/// [compilation predicate][crate#compilation-predicates].
///
/// # Examples
/// ```
/// use devela_macros::cif;
///
/// let the_answer_is = if cif!(none(some)) {
/// "one"
/// } else if cif!(any(false, diff(this, that))) {
/// "two"
/// } else {
/// "three"
/// };
///
/// assert_eq!(the_answer_is, "two");
/// ```
#[proc_macro]
#[cfg(feature = "alloc")]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "alloc")))]
pub fn cif(input: TokenStream) -> TokenStream {
let input = input.to_string();
let result = compile_eval(input);
result.to_string().parse().unwrap()
}