zalgo_codec_macro/
lib.rs

1//! This crate provides the proc-macro part of the crate [`zalgo-codec`](https://docs.rs/zalgo-codec/latest/zalgo_codec/)
2//! by defining the procedural macro [`zalgo_embed!`].
3//!
4//! It lets you take source code that's been converted into a single grapheme cluster by the
5//! [`zalgo-codec-common`](https://docs.rs/zalgo-codec-common/latest/zalgo_codec_common/) crate
6//! and compile it as if it was never zalgo-ified.
7//!
8//! # Example
9//!
10//! If we run [`zalgo_encode`] on the text
11//! `fn add(x: i32, y: i32) -> i32 {x + y}` we can add the `add` function to our program
12//! by putting the resulting grapheme cluster inside [`zalgo_embed!`]:
13//! ```
14//! # use zalgo_codec_macro::zalgo_embed;
15//! zalgo_embed!("E͎͉͙͉̞͉͙͆̀́̈́̈́̈̀̓̒̌̀̀̓̒̉̀̍̀̓̒̀͛̀̋̀͘̚̚͘͝");
16//! assert_eq!(add(10, 20), 30);
17//! ```
18
19#![no_std]
20#![forbid(unsafe_code)]
21
22extern crate alloc;
23
24use alloc::format;
25use proc_macro::TokenStream;
26use syn::{parse_macro_input, spanned::Spanned, Error, LitStr};
27
28use zalgo_codec_common::{zalgo_decode, zalgo_encode};
29
30/// 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)
31/// and passes the results on to the compiler.
32///
33/// 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`.
34///
35/// # Examples
36///
37/// We can use a function created in encoded source code:
38/// ```
39/// # use zalgo_codec_common::zalgo_encode;
40/// # use zalgo_codec_macro::zalgo_embed;
41/// // This line expands to the code
42/// // `fn add(x: i32, y: i32) -> i32 {x + y}`
43/// zalgo_embed!("E͎͉͙͉̞͉͙͆̀́̈́̈́̈̀̓̒̌̀̀̓̒̉̀̍̀̓̒̀͛̀̋̀͘̚̚͘͝");
44///
45/// // Now the `add` function is available
46/// assert_eq!(add(10, 20), 30);
47/// ```
48///
49/// It works on expressions too!
50/// ```
51/// # use zalgo_codec_common::zalgo_encode;
52/// # use zalgo_codec_macro::zalgo_embed;
53/// let x = 20;
54/// let y = -10;
55///
56/// // This macro is expanded to the code
57/// // `x + y`
58/// let z = zalgo_embed!("È͙̋̀͘");
59/// assert_eq!(z, x + y);
60/// ```
61///
62/// A more complex example is this program which expands to code that reads the
63/// command line input, encodes it, and prints out the result.
64/// ```
65/// use zalgo_codec_common::{zalgo_encode, EncodeError};
66/// use zalgo_codec_macro::zalgo_embed;
67///
68/// fn main() -> Result<(), EncodeError> {
69///     // This macro expands to
70///     // let input = std::env::args().collect::<Vec<_>>()[1..].join(" ");
71///     // let output = zalgo_encode(&input)?;
72///     // println!("{}", output);
73///     zalgo_embed!("E͔͉͎͕͔̝͓͔͎͖͇͓͌̀͐̀̀̈́́͒̈̉̎̓̚̚̚̚ͅͅ͏̶͔̜̜̞̞̻͌͌̓̓̿̈̉̑̎̎̽̎͊̚̚ͅͅ͏̛͉͎͔̈̂̀̂̉ͯ͌̀ͅ͏͕͔͕͔̝͚͇͐̀̀́͌͏͎̿̓ͅ͏̛͉͎͕͔̟͉͎͔͎̼͎̼͎̈́̈̆͐̉ͯ͐͒͌́̈̂͛̂̌̀͝ͅ͏̛͕͔͕͔͐̉");
74///     Ok(())
75/// }
76/// ```
77/// Do the opposite of [`obfstr`](https://docs.rs/obfstr/latest/obfstr/): obfuscate a string while coding and deobfuscate it during compile time
78/// ```
79/// # use zalgo_codec_macro::zalgo_embed;
80/// const SECRET: &str = zalgo_embed!("Ê̤͏͎͔͔͈͉͓͍̇̀͒́̈́̀̀ͅ͏͍́̂");
81///
82/// assert_eq!(SECRET, "Don't read this mom!");
83/// ```
84/// To do something more like `obfstr`, use [`zalgofy!`].
85///
86/// # Errors
87///
88/// This macro utilizes [`zalgo_decode`] internally, and converts its errors
89/// into compile errors. E.g. it will most likely result in a compile error if you
90/// use this macro on a string that was not generated by [`zalgo_encode`]:
91/// ```compile_fail
92/// # use zalgo_codec_macro::zalgo_embed;
93/// // compile error: the given string decodes into an invalid utf-8 sequence of 1 bytes from index 0
94/// zalgo_embed!("Zalgo");
95/// ```
96#[proc_macro]
97pub fn zalgo_embed(encoded: TokenStream) -> TokenStream {
98    let encoded = parse_macro_input!(encoded as LitStr).value();
99
100    match zalgo_decode(&encoded) {
101        Ok(decoded) => match decoded.parse() {
102            Ok(token_stream) => token_stream,
103            Err(e) => Error::new(encoded.span(), e).into_compile_error().into(),
104        },
105        Err(e) => Error::new(
106            encoded.span(),
107            format!("the given string decodes into an {e}"),
108        )
109        .into_compile_error()
110        .into(),
111    }
112}
113
114/// At compile time this proc-macro encodes the given string literal
115/// as a single grapheme cluster.
116///
117/// # Example
118///
119/// Basic usage:
120/// ```
121/// # use zalgo_codec_macro::zalgofy;
122/// const ZS: &str = zalgofy!("Zalgo");
123/// assert_eq!(ZS, "É̺͇͌͏");
124/// ```
125///
126/// # Errors
127///
128/// This macro uses [`zalgo_encode`] internally and converts its errors into compile errors.
129/// As such it gives a compile error if any character in the string is not either
130/// a printable ACII or newline character.
131///
132/// ```compile_fail
133/// # use zalgo_codec_macro::zalgofy;
134/// // compile error: "can not encode '€' character at string index 4, on line 2 at column 3"
135/// const ZS: &str = zalgofy!(
136/// r"a
137/// ae€"
138/// );
139/// ```
140#[proc_macro]
141pub fn zalgofy(string: TokenStream) -> TokenStream {
142    let string = parse_macro_input!(string as LitStr).value();
143    match zalgo_encode(&string) {
144        Ok(encoded) => {
145            let string = format!("\"{encoded}\"");
146            match string.parse() {
147                Ok(token_stream) => token_stream,
148                Err(e) => Error::new(string.span(), e).into_compile_error().into(),
149            }
150        }
151        Err(e) => Error::new(string.span(), e).to_compile_error().into(),
152    }
153}