use std::collections::HashMap;
use std::io::Read;
use flate2::read::ZlibDecoder;
use crate::error::{Error, Result};
const US_LEXICON: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/misaki/en_us.lex.zlib"
));
const GB_LEXICON: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/misaki/en_gb.lex.zlib"
));
const MAGIC: &[u8; 4] = b"MSKL";
const FORMAT_VERSION: u8 = 1;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Entry {
Plain(String),
Tagged(Vec<(String, Option<String>)>),
}
impl Entry {
pub(crate) fn tagged_get(&self, tag: &str) -> Option<Option<&str>> {
match self {
Entry::Plain(_) => None,
Entry::Tagged(pairs) => pairs
.iter()
.find(|(t, _)| t == tag)
.map(|(_, v)| v.as_deref()),
}
}
}
pub(crate) type Dictionary = HashMap<String, Entry>;
fn corrupt(what: &str) -> Error {
Error::DataError {
reason: format!("embedded misaki lexicon is corrupt: {what}"),
}
}
struct Reader<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn u8(&mut self) -> Result<u8> {
let b = *self.buf.get(self.pos).ok_or_else(|| corrupt("truncated"))?;
self.pos += 1;
Ok(b)
}
fn bytes(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self
.pos
.checked_add(n)
.ok_or_else(|| corrupt("length overflow"))?;
let out = self
.buf
.get(self.pos..end)
.ok_or_else(|| corrupt("truncated"))?;
self.pos = end;
Ok(out)
}
fn u32_le(&mut self) -> Result<u32> {
let b = self.bytes(4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn varint(&mut self) -> Result<usize> {
let mut out = 0usize;
for shift in (0..35).step_by(7) {
let b = self.u8()?;
out |= usize::from(b & 0x7F) << shift;
if b & 0x80 == 0 {
return Ok(out);
}
}
Err(corrupt("varint too long"))
}
fn string(&mut self) -> Result<String> {
let n = self.varint()?;
let raw = self.bytes(n)?;
String::from_utf8(raw.to_vec()).map_err(|_| corrupt("invalid UTF-8"))
}
}
fn read_section(r: &mut Reader, tags: &[String]) -> Result<Dictionary> {
let count = r.u32_le()? as usize;
let mut dict = HashMap::with_capacity(count + count / 2);
let mut prev: Vec<u8> = Vec::new();
for _ in 0..count {
let shared = usize::from(r.u8()?);
if shared > prev.len() {
return Err(corrupt("shared prefix longer than previous key"));
}
let n = r.varint()?;
let suffix = r.bytes(n)?;
prev.truncate(shared);
prev.extend_from_slice(suffix);
let key = String::from_utf8(prev.clone()).map_err(|_| corrupt("invalid UTF-8 key"))?;
let entry = match r.u8()? {
0 => Entry::Plain(r.string()?),
1 => {
let n = r.u8()?;
let mut pairs = Vec::with_capacity(usize::from(n));
for _ in 0..n {
let tag = tags
.get(usize::from(r.u8()?))
.ok_or_else(|| corrupt("tag index out of range"))?
.clone();
let value = match r.u8()? {
0 => None,
_ => Some(r.string()?),
};
pairs.push((tag, value));
}
Entry::Tagged(pairs)
}
_ => return Err(corrupt("unknown entry kind")),
};
dict.insert(key, entry);
}
Ok(dict)
}
fn ascii_capitalize(k: &str) -> String {
let mut out = k.to_ascii_lowercase();
if let Some(first) = out.get_mut(..1) {
first.make_ascii_uppercase();
}
out
}
fn grow_dictionary(dict: &mut Dictionary) {
let mut extra: Vec<(String, Entry)> = Vec::new();
for (k, v) in dict.iter() {
if k.len() < 2 {
continue;
}
let lower = k.to_ascii_lowercase();
if *k == lower {
let cap = ascii_capitalize(k);
if cap != *k {
extra.push((cap, v.clone()));
}
} else if *k == ascii_capitalize(&lower) {
extra.push((lower, v.clone()));
}
}
for (k, v) in extra {
dict.entry(k).or_insert(v);
}
}
pub(crate) fn load(british: bool) -> Result<(Dictionary, Dictionary)> {
let packed = if british { GB_LEXICON } else { US_LEXICON };
let mut raw = Vec::new();
ZlibDecoder::new(packed)
.read_to_end(&mut raw)
.map_err(|e| corrupt(&format!("zlib: {e}")))?;
let mut r = Reader { buf: &raw, pos: 0 };
if r.bytes(4)? != MAGIC.as_slice() {
return Err(corrupt("bad magic"));
}
if r.u8()? != FORMAT_VERSION {
return Err(corrupt("unsupported format version"));
}
let ntags = r.u8()?;
let mut tags = Vec::with_capacity(usize::from(ntags));
for _ in 0..ntags {
tags.push(r.string()?);
}
let mut gold = read_section(&mut r, &tags)?;
let mut silver = read_section(&mut r, &tags)?;
if r.pos != raw.len() {
return Err(corrupt("trailing bytes"));
}
if !gold.keys().chain(silver.keys()).all(|k| k.is_ascii()) {
return Err(corrupt("non-ASCII key"));
}
grow_dictionary(&mut gold);
grow_dictionary(&mut silver);
Ok((gold, silver))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capitalize_is_ascii() {
assert_eq!(ascii_capitalize("hello"), "Hello");
assert_eq!(ascii_capitalize("HeLLo"), "Hello");
assert_eq!(ascii_capitalize("'s"), "'s");
}
#[test]
fn embedded_lexicons_decode_and_grow() {
for british in [false, true] {
let (gold, silver) = match load(british) {
Ok(d) => d,
Err(e) => panic!("load british={british}: {e}"),
};
assert!(gold.len() > 150_000 && silver.len() > 150_000);
assert!(matches!(gold.get("used"), Some(Entry::Tagged(_))));
assert_eq!(gold.get("Hello"), gold.get("hello"));
}
}
}