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}