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

/// Encodes the input string as a wide string (utf-16) constant.
///
/// The type of the returned constant is `&'static [u16; LEN]`.
///
/// # Examples
///
/// ```
/// let expected = &['W' as u16, 'i' as u16, 'd' as u16, 'e' as u16, 0];
/// assert_eq!(expected, obfstr::wide!("Wide\0"));
/// ```
#[macro_export]
macro_rules! wide {
	($s:expr) => {{
		const _WIDE_STRING: &str = $s;
		const _WIDE_LEN: usize = $crate::wide::len(_WIDE_STRING);
		const _WIDE_WORDS: [u16; _WIDE_LEN] = $crate::wide::encode::<_WIDE_LEN>(_WIDE_STRING);
		&_WIDE_WORDS
	}};
}

const fn next(bytes: &[u8]) -> Option<(u32, &[u8])> {
	match bytes {
		&[a, ref tail @ ..] if a & 0x80 == 0x00 =>
			Some((a as u32, tail)),
		&[a, b, ref tail @ ..] if a & 0xe0 == 0xc0 =>
			Some(((a as u32 & 0x1f) << 6 | (b as u32 & 0x3f), tail)),
		&[a, b, c, ref tail @ ..] if a & 0xf0 == 0xe0 =>
			Some(((a as u32 & 0x0f) << 12 | (b as u32 & 0x3f) << 6 | (c as u32 & 0x3f), tail)),
		&[a, b, c, d, ref tail @ ..] if a & 0xf8 == 0xf0 =>
			Some(((a as u32 & 0x07) << 18 | (b as u32 & 0x3f) << 12 | (c as u32 & 0x3f) << 6 | (d as u32 & 0x3f), tail)),
		&[..] => None,
	}
}

#[doc(hidden)]
pub const fn len(s: &str) -> usize {
	let mut bytes = s.as_bytes();
	let mut len = 0;
	while let Some((chr, tail)) = next(bytes) {
		bytes = tail;
		len += if chr >= 0x10000 { 2 } else { 1 };
	}
	return len;
}

#[doc(hidden)]
pub const fn encode<const LEN: usize>(s: &str) -> [u16; LEN] {
	let mut bytes = s.as_bytes();
	let mut data = [0u16; LEN];
	let mut i = 0usize;
	while let Some((chr, tail)) = next(bytes) {
		bytes = tail;
		if chr >= 0x10000 {
			data[i + 0] = (0xD800 + (chr - 0x10000) / 0x400) as u16;
			data[i + 1] = (0xDC00 + (chr - 0x10000) % 0x400) as u16;
			i += 2;
		}
		else {
			data[i] = chr as u16;
			i += 1;
		}
	}
	return data;
}

#[test]
fn test_example() {
	let text = &['e' as u16, 'x' as u16, 'a' as u16, 'm' as u16, 'p' as u16, 'l' as u16, 'e' as u16];
	assert_eq!(text, wide!("example"));
}

#[test]
fn test_escapes() {
	let text = &['\t' as u16, '\n' as u16, '\r' as u16, '\\' as u16, '\0' as u16, '\'' as u16, '\"' as u16, '\x52' as u16, '\u{00B6}' as u16];
	assert_eq!(text, wide!("\t\n\r\\\0\'\"\x52\u{00B6}"));
}

#[test]
fn test_raw() {
	let text = &[b'\\' as u16];
	assert_eq!(text, wide!(r"\"));
	assert_eq!(text, wide!(r#"\"#));
	assert_eq!(text, wide!(r##"\"##));
}

#[test]
fn test_const() {
	const STRING: &str = "Wide\0";
	let text = &['W' as u16, 'i' as u16, 'd' as u16, 'e' as u16, 0];
	assert_eq!(text, wide!(STRING));
}