///| The type of a word (16-bit) appears in a UTF-16 sequence:
///
/// - `BMP`: Basic Multilingual Plane. (including `0x0000–0xD7FF` and `0xE000–0xFFFF`)
/// - `HighSurrogate`: The first word of a surrogate pair. (including `0xD800–0xDBFF`)
/// - `LowSurrogate`: The second word of a surrogate pair. (including `0xDC00–0xDFFF`)
pub(all) enum Utf16CodePoint {
BMP(Char)
HighSurrogate(Char)
LowSurrogate(Char)
} derive(Eq, Show)
///|
pub fn Utf16CodePoint::to_len(self : Utf16CodePoint) -> Int {
match self {
HighSurrogate(_) => 2
_ => 1
}
}
///|
pub fn Utf16CodePoint::to_char(self : Utf16CodePoint) -> Char {
match self {
HighSurrogate(char) | LowSurrogate(char) | BMP(char) => char
}
}
///|
pub fn Utf16CodePoint::to_tuple(self : Utf16CodePoint) -> (Char, Int) {
match self {
HighSurrogate(char) => (char, 2)
LowSurrogate(char) | BMP(char) => (char, 1)
}
}
///| Get the char appeared at n-th word (16-bit) of input string.
///
/// # Examples
///
/// Iterate a string including `U+10000` by valid chars (unicode code point) using a `while` loop:
///
///
/// # Panics
///
/// Panics if index out of range, or any invalid value (code point or surrogate pair) appears at specified position.
pub fn[T : GetUtf16CodePoint] get_utf16_char_at(
input : T,
idx_word : Int
) -> Utf16CodePoint {
&GetUtf16CodePoint::get_utf16(input, idx_word)
}
///|
test "" {
let str = "\u{6708}\u{1f430}\u{5154}" // 月🐰兔
let mut i = 0
while i < str.length() {
let ty = get_utf16_char_at(str, i)
// println(ch)
// advance by 2 words on a surrogate pair
i += if ty is Utf16CodePoint::BMP(_) { 1 } else { 2 }
}
}
///|
fn _get_utf16_char_at(input : String, idx_word : Int) -> Utf16CodePoint {
input |> get_utf16_char_at(idx_word)
}
///|
test "get_utf16_char_at" {
// standard test
let str = "a\u{6587}\u{10000}\u{1F408}"
let expected = [
('a', BMP('a')),
('\u{6587}', BMP('\u{6587}')),
('\u{10000}', HighSurrogate('\u{10000}')),
('\u{10000}', LowSurrogate('\u{10000}')),
('\u{1F408}', HighSurrogate('\u{1F408}')),
('\u{1F408}', LowSurrogate('\u{1F408}')),
]
for i = 0; i < str.length(); i = i + 1 {
let ch = _get_utf16_char_at(str, i)
// assert_eq!(ch.to_char(), expected[i].0)
assert_eq(ch, expected[i].1)
}
inspect("👨🏿🦱".length(), content="7")
inspect(
_get_utf16_char_at("👨🏿🦱", 0),
content="HighSurrogate('👨')",
)
inspect(
_get_utf16_char_at("👨🏿🦱", 1),
content="LowSurrogate('👨')",
)
inspect(
_get_utf16_char_at("👨🏿🦱", 2),
content="HighSurrogate('🏿')",
)
inspect(
_get_utf16_char_at("👨🏿🦱", 3),
content="LowSurrogate('🏿')",
)
inspect(_get_utf16_char_at("👨🏿🦱", 4), content="BMP('\\u{200d}')")
inspect(
_get_utf16_char_at("👨🏿🦱", 5),
content="HighSurrogate('🦱')",
)
inspect(
_get_utf16_char_at("👨🏿🦱", 6),
content="LowSurrogate('🦱')",
)
// doctest (keep this part same to the doc comments)
let str = "\u{6708}\u{1f430}\u{5154}" // 月🐰兔
let mut i = 0
while i < str.length() {
let ch = _get_utf16_char_at(str, i)
// println(ch) // comment out to omit display
// advance by 2 words on a surrogate pair
i += ch.to_len()
}
assert_eq(i, 4)
}
///|
pub fn combine_surrogate_pair_int(high : Int, low : Int) -> Char {
if (high & 0xFFFFFC00) != 0x0000D800 {
if DEBUGGER {
abort("invalid high surrogate \{high}")
} else {
panic()
}
} else if (low & 0xFFFFFC00) != 0x0000DC00 {
if DEBUGGER {
abort("invalid low surrogate \{low}")
} else {
panic()
}
}
let codepoint = ((high & 0x000003FF) << 10) + (low & 0x000003FF) + 0x00010000
codepoint.unsafe_to_char()
}
///|
pub fn safe_string_each(str : String, f : (Char) -> Unit) -> Unit {
let mut cursor = 0
while cursor < str.length() {
let char = get_utf16_char_at(str, cursor)
f(char.to_char())
cursor += char.to_len()
}
}
///|
pub fn safe_string_eachi(str : String, f : (Int, Char) -> Unit) -> Unit {
let mut cursor = 0
while cursor < str.length() {
let char = get_utf16_char_at(str, cursor)
f(cursor, char.to_char())
cursor += char.to_len()
}
}
///|
pub fn[A : ToString] to_string(target : A) -> String {
target |> ToString::to_string()
}