use std::{collections::hash_map, sync::Mutex};
use dashmap::DashMap;
use oxc_ast::{AstBuilder, ast::*};
use oxc_str::CompactStr;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{OptimizerError, property_names::base54::base54};
mod base54;
pub struct PropertyMap {
regex: Option<regex::Regex>,
index: DashMap<Box<str>, CompactStr>,
used: Mutex<UsedIds>,
}
#[derive(Default)]
struct UsedIds {
index: FxHashSet<CompactStr>,
next_id: u32,
}
impl PropertyMap {
pub fn new(regex: Option<regex::Regex>) -> Self {
let used = Mutex::new(UsedIds::default());
add_reserved_keywords(&mut used.lock().unwrap().index);
Self { regex, index: DashMap::default(), used }
}
pub fn import(&mut self, data: &[u8]) -> Result<(), OptimizerError> {
{
let mut used = self.used.lock().unwrap();
used.next_id = 0;
used.index.clear();
self.index.clear();
for (i, line) in data.split(|c| *c == b'\n').enumerate() {
let line = line.trim_ascii();
let Ok(line) = str::from_utf8(line) else {
return Err(OptimizerError::PropertyMapParseError(format!(
"invalid utf8 at line '{}'",
i + 1
)));
};
if !line.is_empty() {
let mut split = line.split('=');
let Some(key) = split.next() else {
return Err(OptimizerError::PropertyMapParseError(format!(
"invalid key at line '{}'",
i + 1
)));
};
let Some(value) = split.next() else {
return Err(OptimizerError::PropertyMapParseError(format!(
"invalid value at line '{}'",
i + 1
)));
};
let v: CompactStr = value.into();
self.index.insert(key.into(), v.clone());
used.index.insert(v);
}
}
}
Ok(())
}
pub fn is_dirty(&self) -> bool {
self.used.lock().unwrap().next_id != 0
}
pub fn export(&self) -> Vec<u8> {
let mut props = Vec::new();
for i in self.index.iter() {
props.push((i.key().to_string(), i.value().to_string()))
}
props.sort_by(|a, b| a.0.cmp(&b.0));
let mut b: Vec<u8> = Vec::new();
for i in &props {
b.extend(i.0.as_bytes());
b.push(b'=');
b.extend(i.1.as_bytes());
b.push(b'\n');
}
b
}
pub fn matches(&self, s: &str) -> bool {
if let Some(re) = &self.regex { re.is_match(s) } else { false }
}
}
pub struct LocalPropertyMap<'a, 'ctx> {
map: &'ctx PropertyMap,
cache: FxHashMap<Str<'a>, Option<Str<'a>>>,
}
impl<'a, 'ctx> LocalPropertyMap<'a, 'ctx> {
pub fn new(map: &'ctx PropertyMap) -> Self {
Self { map, cache: FxHashMap::default() }
}
pub fn get(&mut self, key: Str<'a>, ast: &AstBuilder<'a>) -> Option<Str<'a>> {
match self.cache.entry(key) {
hash_map::Entry::Occupied(cache_entry) => *cache_entry.get(),
hash_map::Entry::Vacant(cache_entry) => {
let uid = match self.map.index.entry(key.as_str().into()) {
dashmap::Entry::Occupied(index_entry) => Some(ast.str(index_entry.get())),
dashmap::Entry::Vacant(index_entry) => {
if !self.map.matches(key.as_str()) {
None
} else {
let mut used = self.map.used.lock().unwrap();
let uid = loop {
let i = used.next_id;
used.next_id += 1;
let s = base54(i);
let uid: CompactStr = s.as_str().into();
if used.index.insert(uid.clone()) {
index_entry.insert(uid);
break ast.str(&s);
}
};
Some(uid)
}
}
};
cache_entry.insert(uid);
uid
}
}
}
}
fn add_reserved_keywords(index: &mut FxHashSet<CompactStr>) {
index.insert("as".into());
index.insert("do".into());
index.insert("if".into());
index.insert("in".into());
index.insert("is".into());
index.insert("of".into());
index.insert("any".into());
index.insert("for".into());
index.insert("get".into());
index.insert("let".into());
index.insert("new".into());
index.insert("out".into());
index.insert("set".into());
index.insert("try".into());
index.insert("var".into());
index.insert("case".into());
index.insert("else".into());
index.insert("enum".into());
index.insert("from".into());
index.insert("meta".into());
index.insert("null".into());
index.insert("this".into());
index.insert("true".into());
index.insert("type".into());
index.insert("void".into());
index.insert("with".into());
}