use hermes_unicode::{
is_high_surrogate, is_low_surrogate, utf16_surrogate_pair_to_code_point,
UNICODE_MAX_VALUE, UNICODE_REPLACEMENT_CHARACTER, UNICODE_SURROGATE_FIRST,
UNICODE_SURROGATE_LAST, UTF16_HIGH_SURROGATE, UTF16_LOW_SURROGATE,
};
pub const UTF8_LINE_TERMINATOR_CHAR0: u8 = 0xe2;
#[inline]
pub fn is_utf8_start(ch: u8) -> bool {
(ch & 0x80) != 0
}
#[inline]
pub fn is_utf8_leading_byte(ch: u8) -> bool {
(ch & 0xC0) == 0xC0
}
#[inline]
pub fn is_utf8_continuation_byte(ch: u8) -> bool {
(ch & 0xC0) == 0x80
}
#[inline]
pub fn match_unicode_line_terminator_offset1(bytes: &[u8]) -> bool {
bytes.len() >= 3
&& bytes[0] == 0xe2
&& bytes[1] == 0x80
&& (bytes[2] == 0xa8 || bytes[2] == 0xa9)
}
#[inline]
fn at(bytes: &[u8], i: usize) -> u32 {
bytes.get(i).copied().unwrap_or(0) as u32
}
pub fn decode_utf8_slow_path<const ALLOW_SURROGATES: bool>(
bytes: &[u8],
i: &mut usize,
mut error: impl FnMut(&str),
) -> u32 {
let ch = at(bytes, *i);
let result: u32;
debug_assert!(is_utf8_start(ch as u8));
if (ch & 0xE0) == 0xC0 {
let ch1 = at(bytes, *i + 1);
if (ch1 & 0xC0) != 0x80 {
*i += 1;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
*i += 2;
result = ((ch & 0x1F) << 6) | (ch1 & 0x3F);
if result <= 0x7F {
error("Non-canonical UTF-8 encoding");
return UNICODE_REPLACEMENT_CHARACTER;
}
} else if (ch & 0xF0) == 0xE0 {
let ch1 = at(bytes, *i + 1);
if (ch1 & 0x40) != 0 || (ch1 & 0x80) == 0 {
*i += 1;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
let ch2 = at(bytes, *i + 2);
if (ch2 & 0x40) != 0 || (ch2 & 0x80) == 0 {
*i += 2;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
*i += 3;
result = ((ch & 0x0F) << 12) | ((ch1 & 0x3F) << 6) | (ch2 & 0x3F);
if result <= 0x7FF {
error("Non-canonical UTF-8 encoding");
return UNICODE_REPLACEMENT_CHARACTER;
}
if result >= UNICODE_SURROGATE_FIRST && result <= UNICODE_SURROGATE_LAST && !ALLOW_SURROGATES
{
error(&format!("Invalid UTF-8 code point 0x{:X}", result));
return UNICODE_REPLACEMENT_CHARACTER;
}
} else if (ch & 0xF8) == 0xF0 {
let ch1 = at(bytes, *i + 1);
if (ch1 & 0x40) != 0 || (ch1 & 0x80) == 0 {
*i += 1;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
let ch2 = at(bytes, *i + 2);
if (ch2 & 0x40) != 0 || (ch2 & 0x80) == 0 {
*i += 2;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
let ch3 = at(bytes, *i + 3);
if (ch3 & 0x40) != 0 || (ch3 & 0x80) == 0 {
*i += 3;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
*i += 4;
result =
((ch & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F);
if result <= 0xFFFF {
error("Non-canonical UTF-8 encoding");
return UNICODE_REPLACEMENT_CHARACTER;
}
if result > UNICODE_MAX_VALUE {
error(&format!("Invalid UTF-8 code point 0x{:X}", result));
return UNICODE_REPLACEMENT_CHARACTER;
}
} else {
*i += 1;
error(&format!("Invalid UTF-8 lead byte 0x{:X}", ch & 0xFF));
return UNICODE_REPLACEMENT_CHARACTER;
}
result
}
#[inline]
pub fn decode_utf8<const ALLOW_SURROGATES: bool>(
bytes: &[u8],
i: &mut usize,
error: impl FnMut(&str),
) -> u32 {
if *i < bytes.len() && (bytes[*i] & 0x80) == 0 {
let c = bytes[*i] as u32;
*i += 1;
return c;
}
decode_utf8_slow_path::<ALLOW_SURROGATES>(bytes, i, error)
}
#[inline]
pub fn encode_utf8(out: &mut Vec<u8>, cp: u32) {
if cp <= 0x7F {
out.push(cp as u8);
} else if cp <= 0x7FF {
out.push(((cp >> 6) & 0x1F) as u8 | 0xC0);
out.push((cp & 0x3F) as u8 | 0x80);
} else if cp <= 0xFFFF {
out.push(((cp >> 12) & 0x0F) as u8 | 0xE0);
out.push(((cp >> 6) & 0x3F) as u8 | 0x80);
out.push((cp & 0x3F) as u8 | 0x80);
} else if cp <= 0x1FFFFF {
out.push(((cp >> 18) & 0x07) as u8 | 0xF0);
out.push(((cp >> 12) & 0x3F) as u8 | 0x80);
out.push(((cp >> 6) & 0x3F) as u8 | 0x80);
out.push((cp & 0x3F) as u8 | 0x80);
} else if cp <= 0x3FFFFFF {
out.push(((cp >> 24) & 0x03) as u8 | 0xF8);
out.push(((cp >> 18) & 0x3F) as u8 | 0x80);
out.push(((cp >> 12) & 0x3F) as u8 | 0x80);
out.push(((cp >> 6) & 0x3F) as u8 | 0x80);
out.push((cp & 0x3F) as u8 | 0x80);
} else {
out.push(((cp >> 30) & 0x01) as u8 | 0xFC);
out.push(((cp >> 24) & 0x3F) as u8 | 0x80);
out.push(((cp >> 18) & 0x3F) as u8 | 0x80);
out.push(((cp >> 12) & 0x3F) as u8 | 0x80);
out.push(((cp >> 6) & 0x3F) as u8 | 0x80);
out.push((cp & 0x3F) as u8 | 0x80);
}
}
#[inline]
pub fn append_unicode_to_storage(storage: &mut Vec<u8>, cp: u32) {
if cp < 0x10000 {
encode_utf8(storage, cp);
} else {
debug_assert!(cp <= UNICODE_MAX_VALUE, "invalid Unicode value");
let cp = cp - 0x10000;
encode_utf8(storage, UTF16_HIGH_SURROGATE + ((cp >> 10) & 0x3FF));
encode_utf8(storage, UTF16_LOW_SURROGATE + (cp & 0x3FF));
}
}
#[inline]
pub fn encode_utf16(out: &mut Vec<u16>, cp: u32) {
if cp < 0x10000 {
out.push(cp as u16);
} else {
debug_assert!(cp <= UNICODE_MAX_VALUE, "invalid Unicode value");
let cp = cp - 0x10000;
out.push((UTF16_HIGH_SURROGATE + ((cp >> 10) & 0x3FF)) as u16);
out.push((UTF16_LOW_SURROGATE + (cp & 0x3FF)) as u16);
}
}
pub fn convert_utf8_with_surrogates_to_utf16(bytes: &[u8]) -> Vec<u16> {
let mut out = Vec::with_capacity(bytes.len());
let mut i = 0usize;
while i < bytes.len() {
let cp = decode_utf8::<true>(bytes, &mut i, |_| {});
encode_utf16(&mut out, cp);
}
out
}
#[inline]
fn convert_to_code_point_at(u16s: &[u16], i: usize) -> (u32, usize) {
let c = u16s[i] as u32;
if is_low_surrogate(c) {
(UNICODE_REPLACEMENT_CHARACTER, 1)
} else if is_high_surrogate(c) {
if i + 1 >= u16s.len() || !is_low_surrogate(u16s[i + 1] as u32) {
(UNICODE_REPLACEMENT_CHARACTER, 1)
} else {
(utf16_surrogate_pair_to_code_point(c, u16s[i + 1] as u32), 2)
}
} else {
(c, 1)
}
}
pub fn convert_utf16_to_utf8_with_replacements(u16s: &[u16]) -> Vec<u8> {
let mut out = Vec::with_capacity(u16s.len());
let mut cur = 0usize;
while cur < u16s.len() {
let c = u16s[cur] as u32;
if c <= 0x7F {
out.push(c as u8);
cur += 1;
continue;
}
let (c32, input_consumed) = convert_to_code_point_at(u16s, cur);
cur += input_consumed;
encode_utf8(&mut out, c32);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classifiers() {
assert!(!is_utf8_start(b'a'));
assert!(is_utf8_start(0xc3));
assert!(is_utf8_leading_byte(0xc3));
assert!(!is_utf8_leading_byte(0x80));
assert!(is_utf8_continuation_byte(0x80));
}
#[test]
fn decode_ascii_and_multibyte() {
let buf = b"a";
let mut i = 0usize;
assert_eq!(decode_utf8::<false>(buf, &mut i, |_| {}), 'a' as u32);
assert_eq!(i, 1);
let buf = b"\xc3\xa9";
let mut i = 0usize;
assert_eq!(decode_utf8::<false>(buf, &mut i, |_| {}), 0x00E9);
assert_eq!(i, 2);
let buf = b"\xf0\x9f\x98\x80";
let mut i = 0usize;
assert_eq!(decode_utf8::<false>(buf, &mut i, |_| {}), 0x1F600);
assert_eq!(i, 4);
}
#[test]
fn line_terminator_match() {
assert!(match_unicode_line_terminator_offset1(b"\xe2\x80\xa8")); assert!(match_unicode_line_terminator_offset1(b"\xe2\x80\xa9")); assert!(!match_unicode_line_terminator_offset1(b"\xe2\x80\xaa"));
}
#[test]
fn encode_basic() {
let mut v = vec![];
encode_utf8(&mut v, 'a' as u32);
assert_eq!(v, b"a");
let mut v = vec![];
encode_utf8(&mut v, 0x00E9);
assert_eq!(v, b"\xc3\xa9"); let mut v = vec![];
encode_utf8(&mut v, 0x4E2D);
assert_eq!(v, b"\xe4\xb8\xad"); }
#[test]
fn append_storage_surrogate_pair() {
let mut v = vec![];
append_unicode_to_storage(&mut v, 0x00E9);
assert_eq!(v, b"\xc3\xa9");
let mut v = vec![];
append_unicode_to_storage(&mut v, 0x1F600);
assert_eq!(v, b"\xed\xa0\xbd\xed\xb8\x80");
}
#[test]
fn utf16_roundtrip_and_replacement() {
let mut v = vec![];
encode_utf16(&mut v, 0x41);
assert_eq!(v, [0x41]);
let mut v = vec![];
encode_utf16(&mut v, 0x1F600);
assert_eq!(v, [0xD83D, 0xDE00]);
let wtf8: &[u8] = b"\xed\xa0\xbd\xed\xb8\x80"; let u16s = convert_utf8_with_surrogates_to_utf16(wtf8);
assert_eq!(u16s, [0xD83D, 0xDE00]);
assert_eq!(
convert_utf16_to_utf8_with_replacements(&[0xD83D, 0xDE00]),
b"\xf0\x9f\x98\x80".to_vec()
);
assert_eq!(
convert_utf16_to_utf8_with_replacements(&[0xD800]),
"\u{FFFD}".as_bytes().to_vec()
); assert_eq!(
convert_utf16_to_utf8_with_replacements(&[0xDC00]),
"\u{FFFD}".as_bytes().to_vec()
); assert_eq!(
convert_utf16_to_utf8_with_replacements(&[0x41, 0x42]),
b"AB".to_vec()
);
}
#[test]
fn invalid_reports_error_and_replacement() {
let buf = b"\xc3\x20"; let mut i = 0usize;
let mut errs = 0;
let cp = decode_utf8::<false>(buf, &mut i, |_| errs += 1);
assert_eq!(cp, 0xFFFD);
assert_eq!(errs, 1);
}
}