Skip to main content

zalgo_codec_common/
lib.rs

1//! A crate for converting a string containing only printable ASCII and newlines
2//! into a single unicode grapheme cluster and back.
3//! Provides the non-macro functionality of the crate [`zalgo-codec`](https://docs.rs/zalgo-codec/latest/zalgo_codec/).
4//!
5//! There are two ways of interacting with the codec.
6//! The first is to call the encoding and decoding functions directly,
7//! and the second is to use the [`ZalgoString`] wrapper type.
8//!
9//! # Examples
10//!
11//! Encode a string to a grapheme cluster with [`zalgo_encode`]:
12//! ```
13//! # use zalgo_codec_common::{EncodeError, zalgo_encode};
14//! let s = "Zalgo";
15//! let encoded = zalgo_encode(s)?;
16//! assert_eq!(encoded, "̺͇́͌͏");
17//! # Ok::<(), EncodeError>(())
18//! ```
19//! Decode a grapheme cluster back into a string:
20//! ```
21//! # use zalgo_codec_common::{zalgo_decode, DecodeError};
22//! let encoded = "̺͇́͌͏";
23//! let s = zalgo_decode(encoded)?;
24//! assert_eq!(s, "Zalgo");
25//! # Ok::<(), DecodeError>(())
26//! ```
27//!
28//! The [`ZalgoString`] type can be used to encode a string and handle the result in various ways:
29//!
30//! ```
31//! # use zalgo_codec_common::{ZalgoString, EncodeError};
32//! # fn main() -> Result<(), EncodeError> {
33//! let s = "Zalgo";
34//! let zstr = ZalgoString::try_from(s)?;
35//!
36//! // Implements PartialEq with common string types
37//! assert_eq!(zstr, "̺͇́͌͏");
38//!
39//! // Utility functions
40//! assert_eq!(zstr.len(), 2 * s.len());
41//! assert_eq!(zstr.decoded_len(), s.len());
42//!
43//! // Iterate over bytes and chars, in both encoded and decoded form
44//! assert_eq!(zstr.bytes().next(), Some(204));
45//! assert_eq!(zstr.decoded_bytes().nth_back(2), Some(b'l'));
46//! assert_eq!(zstr.chars().nth(1), Some('\u{341}'));
47//! assert_eq!(zstr.decoded_chars().next_back(), Some('o'));
48//!
49//! // Decode inplace
50//! assert_eq!(zstr.into_decoded_string(), "Zalgo");
51//! # Ok(())
52//! # }
53//! ```
54//!
55//! # Feature flags
56//!
57//! `std`: enables [`EncodeError`] and [`DecodeError`] to capture a [`Backtrace`](std::backtrace::Backtrace).
58//! If this feature is not enabled the library is `no_std` compatible, but still uses the `alloc` crate.
59//!
60//! `serde`: derives the [`serde::Serialize`] and [`serde::Deserialize`] traits
61//! from [`serde`] for [`ZalgoString`].
62//!
63//! `rkyv`: derives the [`rkyv::Serialize`], [`rkyv::Deserialize`], and [`rkyv::Archive`] traits from [`rkyv`] for [`ZalgoString`].
64//!
65//! # Explanation
66//!
67//! Characters U+0300–U+036F are the combining characters for unicode Latin.
68//! The fun thing about combining characters is that you can add as many of these characters
69//! as you like to the original character and it does not create any new symbols,
70//! it only adds symbols on top of the character. It's supposed to be used in order to
71//! create characters such as `á` by taking a normal `a` and adding another character
72//! to give it the mark (U+301, in this case). Fun fact: Unicode doesn't specify
73//! any limit on the number of these characters.
74//! Conveniently, this gives us 112 different characters we can map to,
75//! which nicely maps to the ASCII character range 0x20 -> 0x7F, aka all the non-control characters.
76//! The only issue is that we can't have new lines in this system, so to fix that,
77//! we can simply map 0x7F (DEL) to 0x0A (LF).
78//! This can be represented as `(CHARACTER - 11) % 133 - 21`, and decoded with `(CHARACTER + 22) % 133 + 10`.
79//!
80//! <details><summary><b>Full conversion table</b></summary>
81//!
82//! | ASCII character | Encoded |  
83//! |---|---|  
84//! | A | U+321 |  
85//! | B | U+322 |  
86//! | C | U+323 |  
87//! | D | U+324 |  
88//! | E | U+325 |  
89//! | F | U+326 |  
90//! | G | U+327 |  
91//! | H | U+328 |  
92//! | I | U+329 |  
93//! | J | U+32A |  
94//! | K | U+32B |  
95//! | L | U+32C |  
96//! | M | U+32D |  
97//! | N | U+32E |  
98//! | O | U+32F |  
99//! | P | U+330 |  
100//! | Q | U+331 |  
101//! | R | U+332 |  
102//! | S | U+333 |  
103//! | T | U+334 |  
104//! | U | U+335 |  
105//! | V | U+336 |  
106//! | W | U+337 |  
107//! | X | U+338 |  
108//! | Y | U+339 |  
109//! | Z | U+33A |  
110//! | a | U+341 |  
111//! | b | U+342 |  
112//! | c | U+343 |  
113//! | d | U+344 |  
114//! | e | U+345 |  
115//! | f | U+346 |  
116//! | g | U+347 |  
117//! | h | U+348 |  
118//! | i | U+349 |  
119//! | j | U+34A |  
120//! | k | U+34B |  
121//! | l | U+34C |  
122//! | m | U+34D |  
123//! | n | U+34E |  
124//! | o | U+34F |  
125//! | p | U+350 |  
126//! | q | U+351 |  
127//! | r | U+352 |  
128//! | s | U+353 |  
129//! | t | U+354 |  
130//! | u | U+355 |  
131//! | v | U+356 |  
132//! | w | U+357 |  
133//! | x | U+358 |  
134//! | y | U+359 |  
135//! | z | U+35A |  
136//! | 1 | U+311 |  
137//! | 2 | U+312 |  
138//! | 3 | U+313 |  
139//! | 4 | U+314 |  
140//! | 5 | U+315 |  
141//! | 6 | U+316 |  
142//! | 7 | U+317 |  
143//! | 8 | U+318 |  
144//! | 9 | U+319 |  
145//! | 0 | U+310 |  
146//! |   | U+300 |  
147//! | ! | U+301 |  
148//! | " | U+302 |  
149//! | # | U+303 |  
150//! | $ | U+304 |  
151//! | % | U+305 |  
152//! | & | U+306 |  
153//! | ' | U+307 |  
154//! | ( | U+308 |  
155//! | ) | U+309 |  
156//! | * | U+30A |  
157//! | + | U+30B |  
158//! | , | U+30C |  
159//! | - | U+30D |  
160//! | \ | U+33C |  
161//! | . | U+30E |  
162//! | / | U+30F |  
163//! | : | U+31A |  
164//! | ; | U+31B |  
165//! | < | U+31C |  
166//! | = | U+31D |  
167//! | > | U+31E |  
168//! | ? | U+31F |  
169//! | @ | U+320 |  
170//! | \n| U+36F |  
171//!
172//! </details>
173//!
174//! # Experiment with the codec
175//!
176//! There is an executable available for experimenting with the codec on text and files.
177//! It can be installed with `cargo install zalgo-codec --features binary`.
178
179#![no_std]
180#![cfg_attr(docsrs, feature(doc_cfg))]
181
182#[cfg(feature = "std")]
183extern crate std;
184
185extern crate alloc;
186use alloc::{format, string::String, vec, vec::Vec};
187use core::{fmt, str};
188
189mod error;
190pub mod zalgo_string;
191
192pub use error::{DecodeError, EncodeError};
193pub use zalgo_string::ZalgoString;
194
195/// Takes in a string slice that consists of only printable ACII and newline characters
196/// and encodes it into a single grapheme cluster using a reversible encoding scheme.
197///
198/// The resulting string is a single unicode grapheme cluster and should
199/// only take up a single character space horizontally when displayed
200/// (though this can vary between platforms depending on how they deal with unicode).
201/// The resulting string will be ~2 times larger than the original in terms of bytes, and it
202/// can be decoded to recover the original string with [`zalgo_decode`].
203///
204/// # Errors
205///
206/// Returns an error if the input contains a byte that does not correspond to a printable
207/// ASCII character or newline.
208/// Notably this means that this function can not encode tab or carriage return characters.
209/// Carriage returns are present in e.g. line endings on Windows.
210///
211/// # Example
212///
213/// Basic usage:
214/// ```
215/// # use zalgo_codec_common::{EncodeError, zalgo_encode};
216/// assert_eq!(zalgo_encode("Zalgo")?, "̺͇́͌͏");
217/// # Ok::<(), EncodeError>(())
218/// ```
219/// Can not encode non-ASCII characters or ASCII control characters except newlines:
220/// ```
221/// # use zalgo_codec_common::zalgo_encode;
222/// assert!(zalgo_encode("Windows line ending: \r\n").is_err());
223/// assert!(zalgo_encode("Zålgö").is_err());
224/// ```
225#[must_use = "the function returns a new value and does not modify the input"]
226pub fn zalgo_encode(string: &str) -> Result<String, EncodeError> {
227    // We will encode this many bytes at a time before pushing onto the result vector.
228    const BATCH_SIZE: usize = 16;
229
230    // The line we are currently encoding
231    let mut line = 1;
232    // The column on that line we are currently encoding
233    let mut column = 1;
234    // These are used for reporting a useful error if the encoding process fails.
235
236    // Every byte in the input will encode to two bytes. The extra byte is for the initial letter
237    // which is there in order for the output to be displayable in an intuitive way.
238    let mut result = Vec::with_capacity(2 * string.len());
239
240    for (i, batch) in string.as_bytes().chunks(BATCH_SIZE).enumerate() {
241        let mut buffer = [0; 2 * BATCH_SIZE];
242        let mut encoded = 0;
243        for (j, byte) in batch.iter().enumerate() {
244            // Only encode ASCII bytes corresponding to printable characters or newlines.
245            if (32..127).contains(byte) || *byte == b'\n' {
246                if *byte == b'\n' {
247                    line += 1;
248                    // `column` is still 1-indexed since it gets incremented at the end of the current loop iteration.
249                    column = 0;
250                }
251
252                let v = ((i16::from(*byte) - 11).rem_euclid(133) - 21) as u8;
253                buffer[encoded] = (v >> 6) & 1 | 0b1100_1100;
254                buffer[encoded + 1] = (v & 63) | 0b1000_0000;
255                encoded += 2;
256                column += 1;
257            } else {
258                let index = i * BATCH_SIZE + j;
259                // The panic should never trigger since we know that string[i*BATCH_SIZE + j]
260                // has some value which is stored in `byte`, and that this value is the first
261                // byte of a non-ascii character and that Strings in Rust are valid utf-8.
262                // All of this means that the value that starts at this index is a utf-8 encoded
263                // character, which `chars.next()` will extract.
264                let unencodable_character = string[index..].chars().next()
265                // TODO: Find a way to get rid of the expect.
266                    .expect("i*BATCH_SIZE + j is within the string and on a char boundary, so string.chars().next() should find a char");
267                return Err(EncodeError::new(unencodable_character, line, column, index));
268            }
269        }
270        result.extend_from_slice(&buffer[..encoded]);
271    }
272
273    // Safety: the encoding process does not produce invalid UTF-8
274    // if given valid printable ASCII + newlines,
275    // which is checked before this point
276    Ok(unsafe { String::from_utf8_unchecked(result) })
277}
278
279/// Takes in a string that was encoded by [`zalgo_encode`] and decodes it back into an ASCII string.
280///
281/// # Errors
282///
283/// Returns an error if the decoded string is not valid UTF-8.
284/// This can happen if the input is empty, or if it is a string that was not encoded by [`zalgo_encode`],
285/// since the byte manipulations that this function does could result in invalid unicode in that case.
286/// Even if no error is returned in such a case the results are not meaningful.
287/// If you want to be able to decode without this check, consider using a [`ZalgoString`].
288///
289/// # Examples
290///
291/// Basic usage:
292/// ```
293/// # use zalgo_codec_common::{zalgo_decode, DecodeError};
294/// assert_eq!(zalgo_decode("̺͇́͌͏")?, "Zalgo");
295/// # Ok::<(), DecodeError>(())
296/// ```
297/// Decoding arbitrary strings that were not produced by [`zalgo_encode`] will most likely lead to errors:
298/// ```
299/// # use zalgo_codec_common::zalgo_decode;
300/// assert!(zalgo_decode("Blorbo goes to space").is_err());
301/// ```
302/// If it doesn't the results are not meaningful:
303/// ```
304/// # use zalgo_codec_common::{zalgo_decode, DecodeError};
305/// assert_eq!(zalgo_decode("awö")?, "\u{12}\u{11}");
306/// # Ok::<(), DecodeError>(())
307/// ```
308pub fn zalgo_decode(encoded: &str) -> Result<String, DecodeError> {
309    if encoded.is_empty() {
310        return Ok(String::new());
311    }
312    let mut res = vec![0; encoded.len() / 2];
313    let bytes = encoded.as_bytes();
314
315    for (write, read) in (0..encoded.len()).step_by(2).enumerate() {
316        match bytes.get(read + 1) {
317            Some(next) => res[write] = decode_byte_pair(bytes[read], *next),
318            None => break,
319        }
320    }
321
322    String::from_utf8(res).map_err(DecodeError::new)
323}
324
325#[inline]
326#[must_use = "the function returns a new value and does not modify its inputs"]
327const fn decode_byte_pair(odd: u8, even: u8) -> u8 {
328    (((odd << 6) & 64 | even & 63) + 22) % 133 + 10
329}
330
331/// zalgo-encodes an ASCII string containing Python code and
332/// wraps it in a decoder that decodes and executes it.
333/// The resulting Python code should retain the functionality of the original.
334///
335/// # Example
336///
337/// Encode a simple hello world program in Python
338/// ```
339/// # use zalgo_codec_common::{EncodeError, zalgo_wrap_python};
340/// let py_hello_world = "print(\"Hello, world!\")\n";
341/// let py_hello_world_enc = zalgo_wrap_python(py_hello_world)?;
342/// assert_eq!(
343///     py_hello_world_enc,
344///     "b='̨͉͎͔͐͒̈̂͌͌ͅ͏̌̀͗͏͒͌̈́́̂̉ͯ'.encode();exec(''.join(chr(((h<<6&64|c&63)+22)%133+10)for h,c in zip(b[0::2],b[1::2])))",
345/// );
346/// # Ok::<(), EncodeError>(())
347/// ```
348/// If the contents of the variable `py_hello_world_enc` in
349/// the above code snippet is saved to a file
350/// you can run it with python and it will produce the output
351/// that is expected of the code in the variable `py_hello_world`.
352/// In the example below the file is named `enc.py`.
353/// ```bash
354/// $ python enc.py
355/// Hello, world!
356/// ```
357///
358/// # Known issues
359///
360/// May not work correctly on python versions before 3.10,
361/// see [this github issue](https://github.com/DaCoolOne/DumbIdeas/issues/1) for more information.
362///
363/// # Errors
364///
365/// Returns an error if the input contains a byte that does not correspond to a printable
366/// ASCII character or newline.
367/// ```
368/// # use zalgo_codec_common::zalgo_wrap_python;
369/// let res = zalgo_wrap_python(r#"print("That will be 5€ please")"#);
370/// assert_eq!(
371///     res.map_err(|e| (e.char(), e.line(), e.column())),
372///     Err(('€', 1, 22)),
373/// );
374/// ```
375pub fn zalgo_wrap_python(python: &str) -> Result<String, EncodeError> {
376    let encoded_string = zalgo_encode(python)?;
377    Ok(format!("b='{encoded_string}'.encode();exec(''.join(chr(((h<<6&64|c&63)+22)%133+10)for h,c in zip(b[0::2],b[1::2])))"))
378}
379
380#[cfg(test)]
381mod test {
382    use super::*;
383
384    #[test]
385    fn test_char() {
386        assert_eq!(zalgo_encode("Zalgo\r").map_err(|e| e.char()), Err('\r'));
387        assert_eq!(zalgo_encode("Zålgo").map_err(|e| e.char()), Err('å'));
388    }
389
390    #[test]
391    fn test_empty_decode() {
392        assert_eq!(zalgo_decode("").ok(), Some(String::new()));
393    }
394
395    #[test]
396    fn verify_conversion_table() {
397        assert_eq!(zalgo_encode("A").unwrap(), "\u{321}");
398        assert_eq!(zalgo_decode("\u{321}").unwrap(), "A");
399        assert_eq!(zalgo_encode("B").unwrap(), "\u{322}");
400        assert_eq!(zalgo_decode("\u{322}").unwrap(), "B");
401        assert_eq!(zalgo_encode("C").unwrap(), "\u{323}");
402        assert_eq!(zalgo_decode("\u{323}").unwrap(), "C");
403        assert_eq!(zalgo_encode("D").unwrap(), "\u{324}");
404        assert_eq!(zalgo_decode("\u{324}").unwrap(), "D");
405        assert_eq!(zalgo_encode("E").unwrap(), "\u{325}");
406        assert_eq!(zalgo_decode("\u{325}").unwrap(), "E");
407        assert_eq!(zalgo_encode("F").unwrap(), "\u{326}");
408        assert_eq!(zalgo_decode("\u{326}").unwrap(), "F");
409        assert_eq!(zalgo_encode("G").unwrap(), "\u{327}");
410        assert_eq!(zalgo_decode("\u{327}").unwrap(), "G");
411        assert_eq!(zalgo_encode("H").unwrap(), "\u{328}");
412        assert_eq!(zalgo_decode("\u{328}").unwrap(), "H");
413        assert_eq!(zalgo_encode("I").unwrap(), "\u{329}");
414        assert_eq!(zalgo_decode("\u{329}").unwrap(), "I");
415        assert_eq!(zalgo_encode("J").unwrap(), "\u{32a}");
416        assert_eq!(zalgo_decode("\u{32a}").unwrap(), "J");
417        assert_eq!(zalgo_encode("K").unwrap(), "\u{32b}");
418        assert_eq!(zalgo_decode("\u{32b}").unwrap(), "K");
419        assert_eq!(zalgo_encode("L").unwrap(), "\u{32c}");
420        assert_eq!(zalgo_decode("\u{32c}").unwrap(), "L");
421        assert_eq!(zalgo_encode("M").unwrap(), "\u{32d}");
422        assert_eq!(zalgo_decode("\u{32d}").unwrap(), "M");
423        assert_eq!(zalgo_encode("N").unwrap(), "\u{32e}");
424        assert_eq!(zalgo_decode("\u{32e}").unwrap(), "N");
425        assert_eq!(zalgo_encode("O").unwrap(), "\u{32f}");
426        assert_eq!(zalgo_decode("\u{32f}").unwrap(), "O");
427        assert_eq!(zalgo_encode("P").unwrap(), "\u{330}");
428        assert_eq!(zalgo_decode("\u{330}").unwrap(), "P");
429        assert_eq!(zalgo_encode("Q").unwrap(), "\u{331}");
430        assert_eq!(zalgo_decode("\u{331}").unwrap(), "Q");
431        assert_eq!(zalgo_encode("R").unwrap(), "\u{332}");
432        assert_eq!(zalgo_decode("\u{332}").unwrap(), "R");
433        assert_eq!(zalgo_encode("S").unwrap(), "\u{333}");
434        assert_eq!(zalgo_decode("\u{333}").unwrap(), "S");
435        assert_eq!(zalgo_encode("T").unwrap(), "\u{334}");
436        assert_eq!(zalgo_decode("\u{334}").unwrap(), "T");
437        assert_eq!(zalgo_encode("U").unwrap(), "\u{335}");
438        assert_eq!(zalgo_decode("\u{335}").unwrap(), "U");
439        assert_eq!(zalgo_encode("V").unwrap(), "\u{336}");
440        assert_eq!(zalgo_decode("\u{336}").unwrap(), "V");
441        assert_eq!(zalgo_encode("W").unwrap(), "\u{337}");
442        assert_eq!(zalgo_decode("\u{337}").unwrap(), "W");
443        assert_eq!(zalgo_encode("X").unwrap(), "\u{338}");
444        assert_eq!(zalgo_decode("\u{338}").unwrap(), "X");
445        assert_eq!(zalgo_encode("Y").unwrap(), "\u{339}");
446        assert_eq!(zalgo_decode("\u{339}").unwrap(), "Y");
447        assert_eq!(zalgo_encode("Z").unwrap(), "\u{33a}");
448        assert_eq!(zalgo_decode("\u{33a}").unwrap(), "Z");
449        assert_eq!(zalgo_encode("a").unwrap(), "\u{341}");
450        assert_eq!(zalgo_decode("\u{341}").unwrap(), "a");
451        assert_eq!(zalgo_encode("b").unwrap(), "\u{342}");
452        assert_eq!(zalgo_decode("\u{342}").unwrap(), "b");
453        assert_eq!(zalgo_encode("c").unwrap(), "\u{343}");
454        assert_eq!(zalgo_decode("\u{343}").unwrap(), "c");
455        assert_eq!(zalgo_encode("d").unwrap(), "\u{344}");
456        assert_eq!(zalgo_decode("\u{344}").unwrap(), "d");
457        assert_eq!(zalgo_encode("e").unwrap(), "\u{345}");
458        assert_eq!(zalgo_decode("\u{345}").unwrap(), "e");
459        assert_eq!(zalgo_encode("f").unwrap(), "\u{346}");
460        assert_eq!(zalgo_decode("\u{346}").unwrap(), "f");
461        assert_eq!(zalgo_encode("g").unwrap(), "\u{347}");
462        assert_eq!(zalgo_decode("\u{347}").unwrap(), "g");
463        assert_eq!(zalgo_encode("h").unwrap(), "\u{348}");
464        assert_eq!(zalgo_decode("\u{348}").unwrap(), "h");
465        assert_eq!(zalgo_encode("i").unwrap(), "\u{349}");
466        assert_eq!(zalgo_decode("\u{349}").unwrap(), "i");
467        assert_eq!(zalgo_encode("j").unwrap(), "\u{34a}");
468        assert_eq!(zalgo_decode("\u{34a}").unwrap(), "j");
469        assert_eq!(zalgo_encode("k").unwrap(), "\u{34b}");
470        assert_eq!(zalgo_decode("\u{34b}").unwrap(), "k");
471        assert_eq!(zalgo_encode("l").unwrap(), "\u{34c}");
472        assert_eq!(zalgo_decode("\u{34c}").unwrap(), "l");
473        assert_eq!(zalgo_encode("m").unwrap(), "\u{34d}");
474        assert_eq!(zalgo_decode("\u{34d}").unwrap(), "m");
475        assert_eq!(zalgo_encode("n").unwrap(), "\u{34e}");
476        assert_eq!(zalgo_decode("\u{34e}").unwrap(), "n");
477        assert_eq!(zalgo_encode("o").unwrap(), "\u{34f}");
478        assert_eq!(zalgo_decode("\u{34f}").unwrap(), "o");
479        assert_eq!(zalgo_encode("p").unwrap(), "\u{350}");
480        assert_eq!(zalgo_decode("\u{350}").unwrap(), "p");
481        assert_eq!(zalgo_encode("q").unwrap(), "\u{351}");
482        assert_eq!(zalgo_decode("\u{351}").unwrap(), "q");
483        assert_eq!(zalgo_encode("r").unwrap(), "\u{352}");
484        assert_eq!(zalgo_decode("\u{352}").unwrap(), "r");
485        assert_eq!(zalgo_encode("s").unwrap(), "\u{353}");
486        assert_eq!(zalgo_decode("\u{353}").unwrap(), "s");
487        assert_eq!(zalgo_encode("t").unwrap(), "\u{354}");
488        assert_eq!(zalgo_decode("\u{354}").unwrap(), "t");
489        assert_eq!(zalgo_encode("u").unwrap(), "\u{355}");
490        assert_eq!(zalgo_decode("\u{355}").unwrap(), "u");
491        assert_eq!(zalgo_encode("v").unwrap(), "\u{356}");
492        assert_eq!(zalgo_decode("\u{356}").unwrap(), "v");
493        assert_eq!(zalgo_encode("w").unwrap(), "\u{357}");
494        assert_eq!(zalgo_decode("\u{357}").unwrap(), "w");
495        assert_eq!(zalgo_encode("x").unwrap(), "\u{358}");
496        assert_eq!(zalgo_decode("\u{358}").unwrap(), "x");
497        assert_eq!(zalgo_encode("y").unwrap(), "\u{359}");
498        assert_eq!(zalgo_decode("\u{359}").unwrap(), "y");
499        assert_eq!(zalgo_encode("z").unwrap(), "\u{35a}");
500        assert_eq!(zalgo_decode("\u{35a}").unwrap(), "z");
501        assert_eq!(zalgo_encode("1").unwrap(), "\u{311}");
502        assert_eq!(zalgo_decode("\u{311}").unwrap(), "1");
503        assert_eq!(zalgo_encode("2").unwrap(), "\u{312}");
504        assert_eq!(zalgo_decode("\u{312}").unwrap(), "2");
505        assert_eq!(zalgo_encode("3").unwrap(), "\u{313}");
506        assert_eq!(zalgo_decode("\u{313}").unwrap(), "3");
507        assert_eq!(zalgo_encode("4").unwrap(), "\u{314}");
508        assert_eq!(zalgo_decode("\u{314}").unwrap(), "4");
509        assert_eq!(zalgo_encode("5").unwrap(), "\u{315}");
510        assert_eq!(zalgo_decode("\u{315}").unwrap(), "5");
511        assert_eq!(zalgo_encode("6").unwrap(), "\u{316}");
512        assert_eq!(zalgo_decode("\u{316}").unwrap(), "6");
513        assert_eq!(zalgo_encode("7").unwrap(), "\u{317}");
514        assert_eq!(zalgo_decode("\u{317}").unwrap(), "7");
515        assert_eq!(zalgo_encode("8").unwrap(), "\u{318}");
516        assert_eq!(zalgo_decode("\u{318}").unwrap(), "8");
517        assert_eq!(zalgo_encode("9").unwrap(), "\u{319}");
518        assert_eq!(zalgo_decode("\u{319}").unwrap(), "9");
519        assert_eq!(zalgo_encode("0").unwrap(), "\u{310}");
520        assert_eq!(zalgo_decode("\u{310}").unwrap(), "0");
521        assert_eq!(zalgo_encode(" ").unwrap(), "\u{300}");
522        assert_eq!(zalgo_decode("\u{300}").unwrap(), " ");
523        assert_eq!(zalgo_encode("!").unwrap(), "\u{301}");
524        assert_eq!(zalgo_decode("\u{301}").unwrap(), "!");
525        assert_eq!(zalgo_encode("\"").unwrap(), "\u{302}");
526        assert_eq!(zalgo_decode("\u{302}").unwrap(), "\"");
527        assert_eq!(zalgo_encode("#").unwrap(), "\u{303}");
528        assert_eq!(zalgo_decode("\u{303}").unwrap(), "#");
529        assert_eq!(zalgo_encode("$").unwrap(), "\u{304}");
530        assert_eq!(zalgo_decode("\u{304}").unwrap(), "$");
531        assert_eq!(zalgo_encode("%").unwrap(), "\u{305}");
532        assert_eq!(zalgo_decode("\u{305}").unwrap(), "%");
533        assert_eq!(zalgo_encode("&").unwrap(), "\u{306}");
534        assert_eq!(zalgo_decode("\u{306}").unwrap(), "&");
535        assert_eq!(zalgo_encode("'").unwrap(), "\u{307}");
536        assert_eq!(zalgo_decode("\u{307}").unwrap(), "'");
537        assert_eq!(zalgo_encode("(").unwrap(), "\u{308}");
538        assert_eq!(zalgo_decode("\u{308}").unwrap(), "(");
539        assert_eq!(zalgo_encode(")").unwrap(), "\u{309}");
540        assert_eq!(zalgo_decode("\u{309}").unwrap(), ")");
541        assert_eq!(zalgo_encode("*").unwrap(), "\u{30a}");
542        assert_eq!(zalgo_decode("\u{30a}").unwrap(), "*");
543        assert_eq!(zalgo_encode("+").unwrap(), "\u{30b}");
544        assert_eq!(zalgo_decode("\u{30b}").unwrap(), "+");
545        assert_eq!(zalgo_encode(",").unwrap(), "\u{30c}");
546        assert_eq!(zalgo_decode("\u{30c}").unwrap(), ",");
547        assert_eq!(zalgo_encode("-").unwrap(), "\u{30d}");
548        assert_eq!(zalgo_decode("\u{30d}").unwrap(), "-");
549        assert_eq!(zalgo_encode("\\").unwrap(), "\u{33c}");
550        assert_eq!(zalgo_decode("\u{33c}").unwrap(), "\\");
551        assert_eq!(zalgo_encode(".").unwrap(), "\u{30e}");
552        assert_eq!(zalgo_decode("\u{30e}").unwrap(), ".");
553        assert_eq!(zalgo_encode("/").unwrap(), "\u{30f}");
554        assert_eq!(zalgo_decode("\u{30f}").unwrap(), "/");
555        assert_eq!(zalgo_encode(":").unwrap(), "\u{31a}");
556        assert_eq!(zalgo_decode("\u{31a}").unwrap(), ":");
557        assert_eq!(zalgo_encode(";").unwrap(), "\u{31b}");
558        assert_eq!(zalgo_decode("\u{31b}").unwrap(), ";");
559        assert_eq!(zalgo_encode("<").unwrap(), "\u{31c}");
560        assert_eq!(zalgo_decode("\u{31c}").unwrap(), "<");
561        assert_eq!(zalgo_encode("=").unwrap(), "\u{31d}");
562        assert_eq!(zalgo_decode("\u{31d}").unwrap(), "=");
563        assert_eq!(zalgo_encode(">").unwrap(), "\u{31e}");
564        assert_eq!(zalgo_decode("\u{31e}").unwrap(), ">");
565        assert_eq!(zalgo_encode("?").unwrap(), "\u{31f}");
566        assert_eq!(zalgo_decode("\u{31f}").unwrap(), "?");
567        assert_eq!(zalgo_encode("@").unwrap(), "\u{320}");
568        assert_eq!(zalgo_decode("\u{320}").unwrap(), "@");
569        assert_eq!(zalgo_encode("\n").unwrap(), "\u{36f}");
570        assert_eq!(zalgo_decode("\u{36f}").unwrap(), "\n");
571    }
572}