use bun_core::strings;
#[derive(Copy, Clone)]
pub struct KV<K: 'static, V> {
pub key: &'static [K],
pub value: V,
}
pub struct ComptimeStringMapWithKeyType<
K: 'static,
V: 'static,
const N: usize,
const LEN_TABLE: usize,
> {
min_len: usize,
max_len: usize,
pub kvs: [KV<K, V>; N],
len_indexes: [usize; LEN_TABLE],
keys_list: [&'static [K]; N],
}
pub type ComptimeStringMap<V, const N: usize, const LEN_TABLE: usize> =
ComptimeStringMapWithKeyType<u8, V, N, LEN_TABLE>;
pub trait HasLength {
fn length(&self) -> usize;
}
impl<T> HasLength for [T] {
#[inline]
fn length(&self) -> usize {
self.len()
}
}
impl<T> HasLength for &[T] {
#[inline]
fn length(&self) -> usize {
(*self).len()
}
}
impl HasLength for &bun_alloc::String {
#[inline]
fn length(&self) -> usize {
bun_alloc::String::length(self)
}
}
impl<K, V, const N: usize, const LEN_TABLE: usize> ComptimeStringMapWithKeyType<K, V, N, LEN_TABLE>
where
K: Copy + Eq + Ord + 'static,
V: Copy + 'static,
{
pub fn new(mut sorted_kvs: [KV<K, V>; N]) -> Self {
sorted_kvs.sort_by(|a, b| {
if a.key.len() != b.key.len() {
return a.key.len().cmp(&b.key.len());
}
a.key.cmp(b.key)
});
let min_len = sorted_kvs[0].key.len();
let max_len = sorted_kvs[N - 1].key.len();
debug_assert_eq!(LEN_TABLE, max_len + 1);
let mut len_indexes = [0usize; LEN_TABLE];
let mut len: usize = 0;
let mut i: usize = 0;
while len <= max_len {
while len > sorted_kvs[i].key.len() {
i += 1;
}
len_indexes[len] = i;
len += 1;
}
let mut keys_list: [&'static [K]; N] = [&[]; N];
for (i, kv) in sorted_kvs.iter().enumerate() {
keys_list[i] = kv.key;
}
Self {
min_len,
max_len,
kvs: sorted_kvs,
len_indexes,
keys_list,
}
}
pub fn keys(&self) -> &[&'static [K]] {
&self.keys_list
}
pub fn has(&self, str: &[K]) -> bool {
self.get(str).is_some()
}
#[inline(always)]
fn len_bucket(&self, len: usize) -> core::ops::Range<usize> {
let start = self.len_indexes[len];
let mut end = start;
while end < N && self.kvs[end].key.len() == len {
end += 1;
}
start..end
}
pub fn get_with_length(&self, str: &[K], len: usize) -> Option<V> {
let core::ops::Range { start, end } = self.len_bucket(len);
for i in start..end {
if str == self.kvs[i].key {
return Some(self.kvs[i].value);
}
}
None
}
pub fn get_with_length_and_eql<I>(
&self,
str: I,
len: usize,
eqls: impl Fn(I, &'static [K]) -> bool,
) -> Option<V>
where
I: Copy,
{
let core::ops::Range { start, end } = self.len_bucket(len);
for i in start..end {
if eqls(str, self.kvs[i].key) {
return Some(self.kvs[i].value);
}
}
None
}
pub fn get(&self, str: &[K]) -> Option<V> {
if str.len() < self.min_len || str.len() > self.max_len {
return None;
}
self.get_with_length(str, str.len())
}
pub fn index_of(&self, str: &[K]) -> Option<usize> {
if str.len() < self.min_len || str.len() > self.max_len {
return None;
}
let len = str.len();
let core::ops::Range { start, end } = self.len_bucket(len);
(start..end).find(|&i| str == self.kvs[i].key)
}
pub fn get_with_eql<I>(&self, input: I, eql: impl Fn(I, &'static [K]) -> bool) -> Option<V>
where
I: Copy + HasLength,
{
let length = input.length();
if length < self.min_len || length > self.max_len {
return None;
}
self.get_with_length_and_eql(input, length, eql)
}
pub fn get_key(&self, value: V) -> Option<&'static [K]>
where
V: PartialEq,
{
for kv in &self.kvs {
if kv.value == value {
return Some(kv.key);
}
}
None
}
}
impl<V, const N: usize, const LEN_TABLE: usize> ComptimeStringMapWithKeyType<u8, V, N, LEN_TABLE>
where
V: Copy + 'static,
{
pub fn from_string(&self, str: &bun_alloc::String) -> Option<V> {
self.get_with_eql(str, bun_alloc::String::eql_comptime)
}
pub fn get_any_case(&self, input: &[u8]) -> Option<V> {
self.get_case_insensitive_with_eql(input, strings::eql_comptime_ignore_len)
}
pub fn get_case_insensitive_with_eql(
&self,
input: &[u8],
eql: impl Fn(&[u8], &'static [u8]) -> bool,
) -> Option<V> {
let length = input.len();
if length < self.min_len || length > self.max_len {
return None;
}
let mut buf = [0u8; 256];
debug_assert!(length <= buf.len());
let lowercased = bun_core::strings::copy_lowercase(input, &mut buf[..length]);
self.get_with_length_and_eql(lowercased, length, eql)
}
}
#[macro_export]
macro_rules! comptime_string_map {
($V:ty, [ $( ($key:expr, $val:expr) ),* $(,)? ]) => {{
::once_cell::sync::Lazy::new(|| {
$crate::comptime_string_map::ComptimeStringMapWithKeyType::<u8, $V, _, _>::new([
$( $crate::comptime_string_map::KV { key: $key, value: $val } ),*
])
})
}};
($V:ty, [ $( ($key:expr) ),* $(,)? ]) => {{
::once_cell::sync::Lazy::new(|| {
$crate::comptime_string_map::ComptimeStringMapWithKeyType::<u8, (), _, _>::new([
$( $crate::comptime_string_map::KV { key: $key, value: () } ),*
])
})
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum TestEnum {
A,
B,
C,
D,
E,
}
fn test_map(map: &ComptimeStringMap<TestEnum, 5, 9>) {
assert_eq!(TestEnum::A, map.get(b"have").unwrap());
assert_eq!(TestEnum::B, map.get(b"nothing").unwrap());
assert!(map.get(b"missing").is_none());
assert_eq!(TestEnum::D, map.get(b"these").unwrap());
assert_eq!(TestEnum::E, map.get(b"samelen").unwrap());
assert!(!map.has(b"missing"));
assert!(map.has(b"these"));
}
#[test]
fn comptime_string_map_list_literal_of_list_literals() {
let map = ComptimeStringMapWithKeyType::<u8, TestEnum, 5, 9>::new([
KV {
key: b"these",
value: TestEnum::D,
},
KV {
key: b"have",
value: TestEnum::A,
},
KV {
key: b"nothing",
value: TestEnum::B,
},
KV {
key: b"incommon",
value: TestEnum::C,
},
KV {
key: b"samelen",
value: TestEnum::E,
},
]);
test_map(&map);
}
#[test]
fn comptime_string_map_array_of_structs() {
let map = ComptimeStringMapWithKeyType::<u8, TestEnum, 5, 9>::new([
KV {
key: b"these",
value: TestEnum::D,
},
KV {
key: b"have",
value: TestEnum::A,
},
KV {
key: b"nothing",
value: TestEnum::B,
},
KV {
key: b"incommon",
value: TestEnum::C,
},
KV {
key: b"samelen",
value: TestEnum::E,
},
]);
test_map(&map);
}
#[test]
fn comptime_string_map_slice_of_structs() {
let map = ComptimeStringMapWithKeyType::<u8, TestEnum, 5, 9>::new([
KV {
key: b"these",
value: TestEnum::D,
},
KV {
key: b"have",
value: TestEnum::A,
},
KV {
key: b"nothing",
value: TestEnum::B,
},
KV {
key: b"incommon",
value: TestEnum::C,
},
KV {
key: b"samelen",
value: TestEnum::E,
},
]);
test_map(&map);
}
fn test_set(map: &ComptimeStringMap<(), 5, 9>) {
assert_eq!((), map.get(b"have").unwrap());
assert_eq!((), map.get(b"nothing").unwrap());
assert!(map.get(b"missing").is_none());
assert_eq!((), map.get(b"these").unwrap());
assert_eq!((), map.get(b"samelen").unwrap());
assert!(!map.has(b"missing"));
assert!(map.has(b"these"));
}
#[test]
fn comptime_string_map_void_value_type_slice_of_structs() {
let map = ComptimeStringMapWithKeyType::<u8, (), 5, 9>::new([
KV {
key: b"these",
value: (),
},
KV {
key: b"have",
value: (),
},
KV {
key: b"nothing",
value: (),
},
KV {
key: b"incommon",
value: (),
},
KV {
key: b"samelen",
value: (),
},
]);
test_set(&map);
}
#[test]
fn comptime_string_map_void_value_type_list_literal_of_list_literals() {
let map = ComptimeStringMapWithKeyType::<u8, (), 5, 9>::new([
KV {
key: b"these",
value: (),
},
KV {
key: b"have",
value: (),
},
KV {
key: b"nothing",
value: (),
},
KV {
key: b"incommon",
value: (),
},
KV {
key: b"samelen",
value: (),
},
]);
test_set(&map);
}
}