use std::collections::HashMap;
use std::u16;
use std::sync::Mutex;
use std::fmt;
use std::str::FromStr;
use std::u64;
use std::cmp::{Ordering, min};
use std::mem;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::{Error, Visitor};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ScopeError {
#[error("Tried to restore cleared scopes, but none were cleared")]
NoClearedScopesToRestore,
}
pub const ATOM_LEN_BITS: u16 = 3;
lazy_static! {
pub static ref SCOPE_REPO: Mutex<ScopeRepository> = Mutex::new(ScopeRepository::new());
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Default, Hash)]
pub struct Scope {
a: u64,
b: u64,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ParseScopeError {
#[error("Too long scope. Scopes can be at most 8 atoms long.")]
TooLong,
#[error("Too many atoms. Max 2^16-2 atoms allowed.")]
TooManyAtoms,
}
#[derive(Debug)]
pub struct ScopeRepository {
atoms: Vec<String>,
atom_index_map: HashMap<String, usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct ScopeStack {
clear_stack: Vec<Vec<Scope>>,
pub scopes: Vec<Scope>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum ClearAmount {
TopN(usize),
All,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ScopeStackOp {
Push(Scope),
Pop(usize),
Clear(ClearAmount),
Restore,
Noop,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BasicScopeStackOp {
Push(Scope),
Pop,
}
fn pack_as_u16s(atoms: &[usize]) -> Result<Scope, ParseScopeError> {
let mut res = Scope { a: 0, b: 0 };
for (i, &n) in atoms.iter().enumerate() {
if n >= (u16::MAX as usize) - 2 {
return Err(ParseScopeError::TooManyAtoms);
}
let small = (n + 1) as u64;
if i < 4 {
let shift = (3 - i) * 16;
res.a |= small << shift;
} else {
let shift = (7 - i) * 16;
res.b |= small << shift;
}
}
Ok(res)
}
impl ScopeRepository {
fn new() -> ScopeRepository {
ScopeRepository {
atoms: Vec::new(),
atom_index_map: HashMap::new(),
}
}
pub fn build(&mut self, s: &str) -> Result<Scope, ParseScopeError> {
if s.is_empty() {
return Ok(Scope { a: 0, b: 0 });
}
let parts: Vec<usize> = s.trim_end_matches('.').split('.').map(|a| self.atom_to_index(a)).collect();
if parts.len() > 8 {
return Err(ParseScopeError::TooManyAtoms);
}
pack_as_u16s(&parts[..])
}
pub fn to_string(&self, scope: Scope) -> String {
let mut s = String::new();
for i in 0..8 {
let atom_number = scope.atom_at(i);
if atom_number == 0 {
break;
}
if i != 0 {
s.push('.');
}
s.push_str(self.atom_str(atom_number));
}
s
}
fn atom_to_index(&mut self, atom: &str) -> usize {
if let Some(index) = self.atom_index_map.get(atom) {
return *index;
}
self.atoms.push(atom.to_owned());
let index = self.atoms.len() - 1;
self.atom_index_map.insert(atom.to_owned(), index);
index
}
pub fn atom_str(&self, atom_number: u16) -> &str {
&self.atoms[(atom_number - 1) as usize]
}
}
impl Scope {
pub fn new(s: &str) -> Result<Scope, ParseScopeError> {
let mut repo = SCOPE_REPO.lock().unwrap();
repo.build(s.trim())
}
pub fn atom_at(self, index: usize) -> u16 {
#[allow(clippy::panic)] let shifted = if index < 4 {
self.a >> ((3 - index) * 16)
} else if index < 8 {
self.b >> ((7 - index) * 16)
} else {
panic!("atom index out of bounds {:?}", index);
};
(shifted & 0xFFFF) as u16
}
#[inline]
fn missing_atoms(self) -> u32 {
let trail = if self.b == 0 {
self.a.trailing_zeros() + 64
} else {
self.b.trailing_zeros()
};
trail / 16
}
#[inline(always)]
pub fn len(self) -> u32 {
8 - self.missing_atoms()
}
pub fn is_empty(self) -> bool {
self.len() == 0
}
pub fn build_string(self) -> String {
let repo = SCOPE_REPO.lock().unwrap();
repo.to_string(self)
}
pub fn is_prefix_of(self, s: Scope) -> bool {
let pref_missing = self.missing_atoms();
let mask: (u64, u64) = if pref_missing == 8 {
(0, 0)
} else if pref_missing == 4 {
(u64::MAX, 0)
} else if pref_missing > 4 {
(u64::MAX << ((pref_missing - 4) * 16), 0)
} else {
(u64::MAX, u64::MAX << (pref_missing * 16))
};
let ax = (self.a ^ s.a) & mask.0;
let bx = (self.b ^ s.b) & mask.1;
ax == 0 && bx == 0
}
}
impl FromStr for Scope {
type Err = ParseScopeError;
fn from_str(s: &str) -> Result<Scope, ParseScopeError> {
Scope::new(s)
}
}
impl fmt::Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = self.build_string();
write!(f, "{}", s)
}
}
impl fmt::Debug for Scope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = self.build_string();
write!(f, "<{}>", s)
}
}
impl Serialize for Scope {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
let s = self.build_string();
serializer.serialize_str(&s)
}
}
impl<'de> Deserialize<'de> for Scope {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
struct ScopeVisitor;
impl<'de> Visitor<'de> for ScopeVisitor {
type Value = Scope;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E>(self, v: &str) -> Result<Scope, E> where E: Error {
Scope::new(v).map_err(|e| Error::custom(format!("Invalid scope: {:?}", e)))
}
}
deserializer.deserialize_str(ScopeVisitor)
}
}
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub struct MatchPower(pub f64);
impl Eq for MatchPower {}
#[allow(clippy::derive_ord_xor_partial_ord)] impl Ord for MatchPower {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
impl ScopeStack {
pub fn new() -> ScopeStack {
ScopeStack {
clear_stack: Vec::new(),
scopes: Vec::new()
}
}
pub fn from_vec(v: Vec<Scope>) -> ScopeStack {
ScopeStack {
clear_stack: Vec::new(),
scopes: v
}
}
#[inline]
pub fn push(&mut self, s: Scope) {
self.scopes.push(s);
}
#[inline]
pub fn pop(&mut self) {
self.scopes.pop();
}
pub fn apply(&mut self, op: &ScopeStackOp) -> Result<(), ScopeError> {
self.apply_with_hook(op, |_,_|{})
}
#[inline]
pub fn apply_with_hook<F>(&mut self, op: &ScopeStackOp, mut hook: F) -> Result<(), ScopeError>
where F: FnMut(BasicScopeStackOp, &[Scope])
{
match *op {
ScopeStackOp::Push(scope) => {
self.scopes.push(scope);
hook(BasicScopeStackOp::Push(scope), self.as_slice());
}
ScopeStackOp::Pop(count) => {
for _ in 0..count {
self.scopes.pop();
hook(BasicScopeStackOp::Pop, self.as_slice());
}
}
ScopeStackOp::Clear(amount) => {
let cleared = match amount {
ClearAmount::TopN(n) => {
let to_leave = self.scopes.len() - min(n, self.scopes.len());
self.scopes.split_off(to_leave)
}
ClearAmount::All => {
let mut cleared = Vec::new();
mem::swap(&mut cleared, &mut self.scopes);
cleared
}
};
let clear_amount = cleared.len();
self.clear_stack.push(cleared);
for _ in 0..clear_amount {
hook(BasicScopeStackOp::Pop, self.as_slice());
}
}
ScopeStackOp::Restore => {
match self.clear_stack.pop() {
Some(ref mut to_push) => {
for s in to_push {
self.scopes.push(*s);
hook(BasicScopeStackOp::Push(*s), self.as_slice());
}
}
None => return Err(ScopeError::NoClearedScopesToRestore),
}
}
ScopeStackOp::Noop => (),
}
Ok(())
}
pub fn debug_print(&self, repo: &ScopeRepository) {
for s in &self.scopes {
print!("{} ", repo.to_string(*s));
}
println!();
}
pub fn bottom_n(&self, n: usize) -> &[Scope] {
&self.scopes[0..n]
}
#[inline]
pub fn as_slice(&self) -> &[Scope] {
&self.scopes[..]
}
#[inline]
pub fn len(&self) -> usize {
self.scopes.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn does_match(&self, stack: &[Scope]) -> Option<MatchPower> {
let mut sel_index: usize = 0;
let mut score: f64 = 0.0;
for (i, scope) in stack.iter().enumerate() {
let sel_scope = self.scopes[sel_index];
if sel_scope.is_prefix_of(*scope) {
let len = sel_scope.len();
score += f64::from(len) * f64::from(ATOM_LEN_BITS * (i as u16)).exp2();
sel_index += 1;
if sel_index >= self.scopes.len() {
return Some(MatchPower(score));
}
}
}
None
}
}
impl FromStr for ScopeStack {
type Err = ParseScopeError;
fn from_str(s: &str) -> Result<ScopeStack, ParseScopeError> {
let mut scopes = Vec::new();
for name in s.split_whitespace() {
scopes.push(Scope::from_str(name)?)
}
Ok(ScopeStack::from_vec(scopes))
}
}
impl fmt::Display for ScopeStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for s in &self.scopes {
write!(f, "{} ", s)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn misc() {
}
#[test]
fn repo_works() {
let mut repo = ScopeRepository::new();
assert_eq!(repo.build("source.php").unwrap(),
repo.build("source.php").unwrap());
assert_eq!(repo.build("source.php.wow.hi.bob.troll.clock.5").unwrap(),
repo.build("source.php.wow.hi.bob.troll.clock.5").unwrap());
assert_eq!(repo.build("").unwrap(), repo.build("").unwrap());
let s1 = repo.build("").unwrap();
assert_eq!(repo.to_string(s1), "");
let s2 = repo.build("source.php.wow").unwrap();
assert_eq!(repo.to_string(s2), "source.php.wow");
assert!(repo.build("source.php").unwrap() != repo.build("source.perl").unwrap());
assert!(repo.build("source.php").unwrap() != repo.build("source.php.wagon").unwrap());
assert_eq!(repo.build("comment.line.").unwrap(),
repo.build("comment.line").unwrap());
}
#[test]
fn global_repo_works() {
use std::str::FromStr;
assert_eq!(Scope::new("source.php").unwrap(),
Scope::new("source.php").unwrap());
assert!(Scope::from_str("1.2.3.4.5.6.7.8").is_ok());
assert!(Scope::from_str("1.2.3.4.5.6.7.8.9").is_err());
}
#[test]
fn prefixes_work() {
assert!(Scope::new("1.2.3.4.5.6.7.8")
.unwrap()
.is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
assert!(Scope::new("1.2.3.4.5.6")
.unwrap()
.is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
assert!(Scope::new("1.2.3.4")
.unwrap()
.is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
assert!(!Scope::new("1.2.3.4.5.6.a")
.unwrap()
.is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
assert!(!Scope::new("1.2.a.4.5.6.7")
.unwrap()
.is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
assert!(!Scope::new("1.2.a.4.5.6.7")
.unwrap()
.is_prefix_of(Scope::new("1.2.3.4.5").unwrap()));
assert!(!Scope::new("1.2.a")
.unwrap()
.is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
}
#[test]
fn matching_works() {
use std::str::FromStr;
assert_eq!(ScopeStack::from_str("string")
.unwrap()
.does_match(ScopeStack::from_str("string.quoted").unwrap().as_slice()),
Some(MatchPower(0o1u64 as f64)));
assert_eq!(ScopeStack::from_str("source")
.unwrap()
.does_match(ScopeStack::from_str("string.quoted").unwrap().as_slice()),
None);
assert_eq!(ScopeStack::from_str("a.b e.f")
.unwrap()
.does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
Some(MatchPower(0o202u64 as f64)));
assert_eq!(ScopeStack::from_str("c e.f")
.unwrap()
.does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
Some(MatchPower(0o210u64 as f64)));
assert_eq!(ScopeStack::from_str("c.d e.f")
.unwrap()
.does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
Some(MatchPower(0o220u64 as f64)));
assert_eq!(ScopeStack::from_str("a.b c e.f")
.unwrap()
.does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
Some(MatchPower(0o212u64 as f64)));
assert_eq!(ScopeStack::from_str("a c.d")
.unwrap()
.does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
Some(MatchPower(0o021u64 as f64)));
assert_eq!(ScopeStack::from_str("a c.d.e")
.unwrap()
.does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
None);
}
}