use std::{collections::HashMap, io::BufRead};
use crate::Error;
pub trait TokenResolver {
fn resolve(&self, token: u16) -> Option<&str>;
fn lookup(&self, _index: u32) -> Option<&str> {
None
}
fn is_empty(&self) -> bool {
false
}
}
impl<S, V> TokenResolver for HashMap<u16, V, S>
where
S: ::std::hash::BuildHasher,
V: AsRef<str>,
{
fn resolve(&self, token: u16) -> Option<&str> {
self.get(&token).map(|x| x.as_ref())
}
fn is_empty(&self) -> bool {
self.is_empty()
}
}
impl<T: TokenResolver> TokenResolver for &'_ T {
fn resolve(&self, token: u16) -> Option<&str> {
(**self).resolve(token)
}
fn lookup(&self, index: u32) -> Option<&str> {
(**self).lookup(index)
}
fn is_empty(&self) -> bool {
(**self).is_empty()
}
}
impl<T: TokenResolver + ?Sized> TokenResolver for Box<T> {
fn resolve(&self, token: u16) -> Option<&str> {
(**self).resolve(token)
}
fn lookup(&self, index: u32) -> Option<&str> {
(**self).lookup(index)
}
fn is_empty(&self) -> bool {
(**self).is_empty()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum FailedResolveStrategy {
Error,
Stringify,
Ignore,
}
pub struct BasicTokenResolver {
indices: Box<[u16]>,
occupied: Box<[u64]>,
values: Vec<Box<str>>,
}
impl BasicTokenResolver {
const TOKEN_COUNT: usize = 1 << u16::BITS;
const OCCUPIED_WORDS: usize = Self::TOKEN_COUNT / u64::BITS as usize;
#[inline]
fn location(token: u16) -> (usize, usize, u64) {
let slot = usize::from(token);
let word = slot / u64::BITS as usize;
let mask = 1 << (slot % u64::BITS as usize);
(slot, word, mask)
}
fn insert(&mut self, token: u16, value: Box<str>) {
let (slot, word, mask) = Self::location(token);
if self.occupied[word] & mask == 0 {
self.indices[slot] = self.values.len() as u16;
self.occupied[word] |= mask;
self.values.push(value);
} else {
self.values[self.indices[slot] as usize] = value;
}
}
pub fn from_text_lines<T>(mut reader: T) -> Result<Self, Error>
where
T: BufRead,
{
let mut resolver = Self {
indices: vec![0; Self::TOKEN_COUNT].into_boxed_slice(),
occupied: vec![0; Self::OCCUPIED_WORDS].into_boxed_slice(),
values: Vec::new(),
};
let mut line = String::new();
let mut pos = 0;
while reader.read_line(&mut line)? != 0 {
let (num, text) = line
.split_once(' ')
.ok_or_else(|| Error::invalid_syntax("expected to split line", pos))?;
let z = u16::from_str_radix(num.trim_start_matches("0x"), 16)
.map_err(|_| Error::invalid_syntax("invalid ironman token", pos))?;
pos += line.len();
resolver.insert(z, Box::from(text.trim_ascii_end()));
line.clear();
}
resolver.values.shrink_to_fit();
Ok(resolver)
}
}
impl TokenResolver for BasicTokenResolver {
#[inline]
fn resolve(&self, token: u16) -> Option<&str> {
let (slot, word, mask) = Self::location(token);
if self.occupied[word] & mask == 0 {
return None;
}
Some(self.values[self.indices[slot] as usize].as_ref())
}
fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_create_resolve() {
let data = b"0xffff my_test_token\n0xeeee my_test_token2";
let resolver = BasicTokenResolver::from_text_lines(&data[..]).unwrap();
assert_eq!(resolver.resolve(0xffff), Some("my_test_token"));
assert_eq!(resolver.resolve(0xeeee), Some("my_test_token2"));
}
#[test]
fn duplicate_token_uses_last_value() {
let data = b"0x1234 first\n0x1234 second";
let resolver = BasicTokenResolver::from_text_lines(&data[..]).unwrap();
assert_eq!(resolver.resolve(0x1234), Some("second"));
}
#[test]
fn supports_token_boundaries() {
let data = b"0x0000 first\n0xffff last";
let resolver = BasicTokenResolver::from_text_lines(&data[..]).unwrap();
assert_eq!(resolver.resolve(0x0000), Some("first"));
assert_eq!(resolver.resolve(0xffff), Some("last"));
assert_eq!(resolver.resolve(0x8000), None);
}
}