#[macro_use]
pub mod constants;
use std::convert::TryFrom;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum LcidLookupError {
#[error("LCID {0}/{0:#06x} has reserved bits set: {1}/{1:#x}")]
ReservedBits(u32, u32),
#[error("LCID {0}/{0:#06x} has sort ID bits set: {1}/{1:#x}")]
SortIdBits(u32, u32),
#[error("LCID {0}/{0:#06x} is reserved ('{1}')")]
Reserved(u32, &'static str),
#[error("LCID {0}/{0:#06x} is reserved (<unknown>)")]
ReservedUnknown(u32),
#[error("LCID {0}/{0:#06x} is neither defined nor reserved")]
NeitherDefinedNorReserved(u32),
#[error("LCID {0}/{0:#06x} is undefined")]
Undefined(u32),
}
#[derive(Error, Debug)]
pub enum NameLookupError {
#[error("Name '{0}' is reserved ({1}/{1:#06x})")]
Reserved(&'static str, u32),
#[error("Name '{0}' is undefined")]
Undefined(String),
}
#[derive(Clone, Copy, Debug)]
#[repr(u32)]
pub enum AnsiCodePage {
Windows874 = 874,
ShiftJIS = 932,
GB2312 = 936,
KsC5601 = 949,
Big5 = 950,
Windows1250 = 1250,
Windows1251 = 1251,
Windows1252 = 1252,
Windows1253 = 1253,
Windows1254 = 1254,
Windows1255 = 1255,
Windows1256 = 1256,
Windows1257 = 1257,
Windows1258 = 1258,
}
impl From<AnsiCodePage> for u32 {
fn from(value: AnsiCodePage) -> u32 {
value as u32
}
}
#[derive(Clone, Debug)]
pub struct LanguageId {
pub name: &'static str,
pub lcid: u32,
pub english_name: &'static str,
pub iso639_two_letter: &'static str,
pub iso639_three_letter: &'static str,
pub windows_three_letter: &'static str,
pub ansi_code_page: Option<AnsiCodePage>,
}
const PRIMARY_LANG_ID_MASK: u32 = 0x3ff;
const SUB_LANG_ID_MASK: u32 = 0x3f;
const RESERVED_BITS_MASK: u32 = 0xfff;
const SORT_ID_BITS_MASK: u32 = 0xf;
const PRIMARY_LANG_ID_SHIFT: u32 = 0;
const SUB_LANG_ID_SHIFT: u32 = 10;
const RESERVED_BITS_SHIFT: u32 = 20;
const SORT_ID_BITS_SHIFT: u32 = 16;
impl LanguageId {
#[inline]
pub fn primary_language_id(&self) -> u32 {
(self.lcid >> PRIMARY_LANG_ID_SHIFT) & PRIMARY_LANG_ID_MASK
}
#[inline]
pub fn sub_language_id(&self) -> u32 {
(self.lcid >> SUB_LANG_ID_SHIFT) & SUB_LANG_ID_MASK
}
}
impl TryFrom<u32> for &'static LanguageId {
type Error = LcidLookupError;
fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
let reserved_bits = (value >> RESERVED_BITS_SHIFT) & RESERVED_BITS_MASK;
if reserved_bits != 0 {
return Err(Self::Error::ReservedBits(value, reserved_bits));
}
let sort_id_bits = (value >> SORT_ID_BITS_SHIFT) & SORT_ID_BITS_MASK;
if sort_id_bits != 0 {
return Err(Self::Error::SortIdBits(value, sort_id_bits));
}
use constants::*;
parse_lcid!(value)
}
}
impl TryFrom<&str> for &'static LanguageId {
type Error = NameLookupError;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
use constants::*;
parse_name!(value)
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use std::convert::TryInto;
use super::*;
#[test]
fn bit_mask_size() {
assert_eq!(PRIMARY_LANG_ID_MASK.count_ones(), 10);
assert_eq!(SUB_LANG_ID_MASK.count_ones(), 6);
assert_eq!(RESERVED_BITS_MASK.count_ones(), 12);
assert_eq!(SORT_ID_BITS_MASK.count_ones(), 4);
}
#[test]
fn bit_mask_pos() {
let primary_lang_id: u32 = 0b0000001111111111;
let sub_lang_id: u32 = 0b1111110000000000;
let reserved_bits: u32 = 0b1111111111110000 << 16;
let sort_id_bits: u32 = 0b0000000000001111 << 16;
assert_eq!(
PRIMARY_LANG_ID_MASK.count_ones(),
primary_lang_id.count_ones()
);
assert_eq!(SUB_LANG_ID_MASK.count_ones(), sub_lang_id.count_ones());
assert_eq!(RESERVED_BITS_MASK.count_ones(), reserved_bits.count_ones());
assert_eq!(SORT_ID_BITS_MASK.count_ones(), sort_id_bits.count_ones());
assert_eq!(
PRIMARY_LANG_ID_MASK << PRIMARY_LANG_ID_SHIFT,
primary_lang_id
);
assert_eq!(SUB_LANG_ID_MASK << SUB_LANG_ID_SHIFT, sub_lang_id);
assert_eq!(RESERVED_BITS_MASK << RESERVED_BITS_SHIFT, reserved_bits);
assert_eq!(SORT_ID_BITS_MASK << SORT_ID_BITS_SHIFT, sort_id_bits);
let all_bits = reserved_bits | sort_id_bits | sub_lang_id | primary_lang_id;
assert_eq!(all_bits, u32::MAX);
let all_count = reserved_bits.count_ones()
+ sort_id_bits.count_ones()
+ sub_lang_id.count_ones()
+ primary_lang_id.count_ones();
assert_eq!(all_count, u32::MAX.count_ones());
}
#[test]
fn lcid_conv_reserved() {
for reserved_bits in 1..=RESERVED_BITS_MASK {
let reserved = reserved_bits << RESERVED_BITS_SHIFT;
let err = TryInto::<&LanguageId>::try_into(reserved).unwrap_err();
assert_matches!(err, LcidLookupError::ReservedBits(v, r) if v == reserved && r == reserved_bits);
}
}
#[test]
fn lcid_conv_sort_id() {
for sort_id_bits in 1..=SORT_ID_BITS_MASK {
let sort_id = sort_id_bits << SORT_ID_BITS_SHIFT;
let err = TryInto::<&LanguageId>::try_into(sort_id).unwrap_err();
assert_matches!(err, LcidLookupError::SortIdBits(v, r) if v == sort_id && r == sort_id_bits);
}
}
#[test]
fn ansi_code_page_to_u32() {
assert_eq!(AnsiCodePage::Windows1252 as u32, 1252u32);
let value: u32 = AnsiCodePage::Windows1252.into();
assert_eq!(value, 1252u32);
}
}