use std::collections::HashMap;
use std::ffi::c_int;
use std::mem::ManuallyDrop;
use std::{fmt, ptr};
use luajit_bindings::{self as lua, ffi::*, Poppable, Pushable};
use super::{KVec, Object, String};
pub type Dictionary = KVec<KeyValuePair>;
#[derive(Clone, PartialEq)]
#[repr(C)]
pub struct KeyValuePair {
pub(crate) key: String,
pub(crate) value: Object,
}
impl fmt::Debug for KeyValuePair {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for KeyValuePair {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.key, self.value)
}
}
impl<K, V> From<(K, V)> for KeyValuePair
where
K: Into<String>,
V: Into<Object>,
{
fn from((k, v): (K, V)) -> Self {
Self { key: k.into(), value: v.into() }
}
}
impl Dictionary {
pub fn get<Q>(&self, query: &Q) -> Option<&Object>
where
String: PartialEq<Q>,
{
self.iter()
.find_map(|pair| (&pair.key == query).then_some(&pair.value))
}
pub fn get_mut<Q>(&mut self, query: &Q) -> Option<&mut Object>
where
String: PartialEq<Q>,
{
self.iter_mut()
.find_map(|pair| (&pair.key == query).then_some(&mut pair.value))
}
}
impl fmt::Debug for Dictionary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_map()
.entries(self.iter().map(|pair| (&pair.key, &pair.value)))
.finish()
}
}
impl fmt::Display for Dictionary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
impl<S: Into<String>> std::ops::Index<S> for Dictionary {
type Output = Object;
fn index(&self, index: S) -> &Self::Output {
self.get(&index.into()).unwrap()
}
}
impl<S: Into<String>> std::ops::IndexMut<S> for Dictionary {
fn index_mut(&mut self, index: S) -> &mut Self::Output {
self.get_mut(&index.into()).unwrap()
}
}
impl Pushable for Dictionary {
unsafe fn push(self, lstate: *mut lua_State) -> Result<c_int, lua::Error> {
lua::ffi::lua_createtable(lstate, 0, self.len() as _);
for (key, obj) in self {
lua::ffi::lua_pushlstring(lstate, key.as_ptr(), key.len());
obj.push(lstate)?;
lua::ffi::lua_rawset(lstate, -3);
}
Ok(1)
}
}
impl Poppable for Dictionary {
unsafe fn pop(lstate: *mut lua_State) -> Result<Self, lua::Error> {
<HashMap<crate::String, Object>>::pop(lstate).map(Into::into)
}
}
impl IntoIterator for Dictionary {
type IntoIter = DictIterator;
type Item = <DictIterator as Iterator>::Item;
#[inline]
fn into_iter(self) -> Self::IntoIter {
let arr = ManuallyDrop::new(self);
let start = arr.items;
let end = unsafe { start.add(arr.len()) };
DictIterator { start, end }
}
}
pub struct DictIterator {
start: *const KeyValuePair,
end: *const KeyValuePair,
}
impl Iterator for DictIterator {
type Item = (String, Object);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.start == self.end {
return None;
}
let current = self.start;
self.start = unsafe { self.start.offset(1) };
let KeyValuePair { key, value } = unsafe { ptr::read(current) };
Some((key, value))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let exact = self.len();
(exact, Some(exact))
}
#[inline]
fn count(self) -> usize {
self.len()
}
}
impl ExactSizeIterator for DictIterator {
#[inline]
fn len(&self) -> usize {
unsafe { self.end.offset_from(self.start) as usize }
}
}
impl DoubleEndedIterator for DictIterator {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.start == self.end {
return None;
}
let current = self.end;
self.end = unsafe { self.end.offset(-1) };
let KeyValuePair { key, value } = unsafe { ptr::read(current) };
Some((key, value))
}
}
impl std::iter::FusedIterator for DictIterator {}
impl Drop for DictIterator {
fn drop(&mut self) {
while self.start != self.end {
unsafe {
ptr::drop_in_place(self.start as *mut Object);
self.start = self.start.offset(1);
}
}
}
}
impl<K, V> FromIterator<(K, V)> for Dictionary
where
K: Into<String>,
V: Into<Object>,
{
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
iter.into_iter()
.map(|(k, v)| (k, v.into()))
.filter(|(_, obj)| obj.is_some())
.map(KeyValuePair::from)
.collect::<Vec<KeyValuePair>>()
.into()
}
}
impl<K, V> From<HashMap<K, V>> for Dictionary
where
String: From<K>,
Object: From<V>,
{
fn from(hashmap: HashMap<K, V>) -> Self {
hashmap.into_iter().collect()
}
}
#[cfg(test)]
mod tests {
use super::{Dictionary, Object, String as NvimString};
#[test]
fn iter_basic() {
let dict = Dictionary::from_iter([
("foo", "Foo"),
("bar", "Bar"),
("baz", "Baz"),
]);
let mut iter = dict.into_iter();
assert_eq!(
Some((NvimString::from("foo"), Object::from("Foo"))),
iter.next()
);
assert_eq!(
Some((NvimString::from("bar"), Object::from("Bar"))),
iter.next()
);
assert_eq!(
Some((NvimString::from("baz"), Object::from("Baz"))),
iter.next()
);
assert_eq!(None, iter.next());
}
#[test]
fn drop_iter_halfway() {
let dict = Dictionary::from_iter([
("foo", "Foo"),
("bar", "Bar"),
("baz", "Baz"),
]);
let mut iter = dict.into_iter();
assert_eq!(
Some((NvimString::from("foo"), Object::from("Foo"))),
iter.next()
);
}
#[test]
fn debug_dict() {
let dict = Dictionary::from_iter([
("a", Object::from(1)),
("b", Object::from(true)),
("c", Object::from("foobar")),
]);
assert_eq!(
String::from("{a: 1, b: true, c: \"foobar\"}"),
format!("{dict}")
);
}
#[test]
fn debug_nested_dict() {
let dict = Dictionary::from_iter([(
"foo",
Object::from(Dictionary::from_iter([("a", 1)])),
)]);
assert_eq!(String::from("{foo: {a: 1}}"), format!("{dict}"));
}
}