zalgo-codec-macro 0.2.0

Provides the zalgo_embed! macro for compiling source code that's been converted into a single grapheme cluster by the zalgo-codec-common crate
Documentation
//! This crate provides the proc-macro part of the crate [`zalgo-codec`](https://docs.rs/zalgo-codec/latest/zalgo_codec/)
//! by defining the procedural macro [`zalgo_embed!`].
//!
//! It lets you take source code that's been converted into a single grapheme cluster by the
//! [`zalgo-codec-common`](https://docs.rs/zalgo-codec-common/latest/zalgo_codec_common/) crate
//! and compile it as if it was never zalgo-ified.
//!
//! # Example
//!
//! If we run [`zalgo_encode`] on the text
//! `fn add(x: i32, y: i32) -> i32 {x + y}` we can add the `add` function to our program
//! by putting the resulting grapheme cluster inside [`zalgo_embed!`]:
//! ```
//! # use zalgo_codec_macro::zalgo_embed;
//! zalgo_embed!("͎͉͙͉̞͉͙͆̀́̈́̈́̈̀̓̒̌̀̀̓̒̉̀̍̀̓̒̀͛̀̋̀͘̚̚͘͝");
//! assert_eq!(add(10, 20), 30);
//! ```

#![no_std]
#![forbid(unsafe_code)]

extern crate alloc;

use alloc::format;
use proc_macro::TokenStream;
use syn::{parse_macro_input, spanned::Spanned, Error, LitStr};

use zalgo_codec_common::{zalgo_decode, zalgo_encode};

/// This macro decodes a string that has been encoded with [`zalgo_encode`](https://docs.rs/zalgo-codec-common/latest/zalgo_codec_common/fn.zalgo_encode.html)
/// and passes the results on to the compiler.
///
/// To generate the encoded string used as input you can use the provided program. It can be installed with `cargo install zalgo-codec --features binary`.
///
/// # Examples
///
/// We can use a function created in encoded source code:
/// ```
/// # use zalgo_codec_common::zalgo_encode;
/// # use zalgo_codec_macro::zalgo_embed;
/// // This line expands to the code
/// // `fn add(x: i32, y: i32) -> i32 {x + y}`
/// zalgo_embed!("͎͉͙͉̞͉͙͆̀́̈́̈́̈̀̓̒̌̀̀̓̒̉̀̍̀̓̒̀͛̀̋̀͘̚̚͘͝");
///
/// // Now the `add` function is available
/// assert_eq!(add(10, 20), 30);
/// ```
///
/// It works on expressions too!
/// ```
/// # use zalgo_codec_common::zalgo_encode;
/// # use zalgo_codec_macro::zalgo_embed;
/// let x = 20;
/// let y = -10;
///
/// // This macro is expanded to the code
/// // `x + y`
/// let z = zalgo_embed!("͙̀̋̀͘");
/// assert_eq!(z, x + y);
/// ```
///
/// A more complex example is this program which expands to code that reads the
/// command line input, encodes it, and prints out the result.
/// ```
/// use zalgo_codec_common::{zalgo_encode, EncodeError};
/// use zalgo_codec_macro::zalgo_embed;
///
/// fn main() -> Result<(), EncodeError> {
///     // This macro expands to
///     // let input = std::env::args().collect::<Vec<_>>()[1..].join(" ");
///     // let output = zalgo_encode(&input)?;
///     // println!("{}", output);
///     zalgo_embed!("͔͉͎͕͔̝͓͔͎͖͇͓͌̀͐̀̀̈́́͒̈̉̎̓̚̚̚̚ͅͅ͏̶͔̜̜̞̞̻͌͌̓̓̿̈̉̑̎̎̽̎͊̚̚ͅͅ͏̛͉͎͔̈̂̀̂̉ͯ͌̀ͅ͏͕͔͕͔̝͚͇͐̀̀́͌͏͎̿̓ͅ͏̛͉͎͕͔̟͉͎͔͎̼͎̼͎̈́̈̆͐̉ͯ͐͒͌́̈̂͛̂̌̀͝ͅ͏̛͕͔͕͔͐̉");
///     Ok(())
/// }
/// ```
/// Do the opposite of [`obfstr`](https://docs.rs/obfstr/latest/obfstr/): obfuscate a string while coding and deobfuscate it during compile time
/// ```
/// # use zalgo_codec_macro::zalgo_embed;
/// const SECRET: &str = zalgo_embed!("̤̂͏͎͔͔͈͉͓͍̇̀͒́̈́̀̀ͅ͏͍́̂");
///
/// assert_eq!(SECRET, "Don't read this mom!");
/// ```
/// To do something more like `obfstr`, use [`zalgofy!`].
///
/// # Errors
///
/// This macro utilizes [`zalgo_decode`] internally, and converts its errors
/// into compile errors. E.g. it will most likely result in a compile error if you
/// use this macro on a string that was not generated by [`zalgo_encode`]:
/// ```compile_fail
/// # use zalgo_codec_macro::zalgo_embed;
/// // compile error: the given string decodes into an invalid utf-8 sequence of 1 bytes from index 0
/// zalgo_embed!("Zalgo");
/// ```
#[proc_macro]
pub fn zalgo_embed(encoded: TokenStream) -> TokenStream {
    let encoded = parse_macro_input!(encoded as LitStr).value();

    match zalgo_decode(&encoded) {
        Ok(decoded) => match decoded.parse() {
            Ok(token_stream) => token_stream,
            Err(e) => Error::new(encoded.span(), e).into_compile_error().into(),
        },
        Err(e) => Error::new(
            encoded.span(),
            format!("the given string decodes into an {e}"),
        )
        .into_compile_error()
        .into(),
    }
}

/// At compile time this proc-macro encodes the given string literal
/// as a single grapheme cluster.
///
/// # Example
///
/// Basic usage:
/// ```
/// # use zalgo_codec_macro::zalgofy;
/// const ZS: &str = zalgofy!("Zalgo");
/// assert_eq!(ZS, "̺͇́͌͏");
/// ```
///
/// # Errors
///
/// This macro uses [`zalgo_encode`] internally and converts its errors into compile errors.
/// As such it gives a compile error if any character in the string is not either
/// a printable ACII or newline character.
///
/// ```compile_fail
/// # use zalgo_codec_macro::zalgofy;
/// // compile error: "can not encode '€' character at string index 4, on line 2 at column 3"
/// const ZS: &str = zalgofy!(
/// r"a
/// ae€"
/// );
/// ```
#[proc_macro]
pub fn zalgofy(string: TokenStream) -> TokenStream {
    let string = parse_macro_input!(string as LitStr).value();
    match zalgo_encode(&string) {
        Ok(encoded) => {
            let string = format!("\"{encoded}\"");
            match string.parse() {
                Ok(token_stream) => token_stream,
                Err(e) => Error::new(string.span(), e).into_compile_error().into(),
            }
        }
        Err(e) => Error::new(string.span(), e).to_compile_error().into(),
    }
}