oxi_types/
dictionary.rs

1use oxi_luajit as lua;
2
3use crate::kvec::{self, KVec};
4use crate::NonOwning;
5use crate::Object;
6
7/// A vector of Neovim
8/// `(`[`String`](crate::String)`, `[`Object`](crate::Object)`)` pairs.
9#[derive(Clone, Default, PartialEq)]
10#[repr(transparent)]
11pub struct Dictionary(pub(super) KVec<KeyValuePair>);
12
13/// A key-value pair mapping a Neovim [`String`] to a Neovim [`Object`].
14//
15// https://github.com/neovim/neovim/blob/v0.9.0/src/nvim/api/private/defs.h#L122-L125
16#[derive(Clone, PartialEq)]
17#[repr(C)]
18pub(super) struct KeyValuePair {
19    key: crate::String,
20    value: Object,
21}
22
23impl core::fmt::Debug for Dictionary {
24    #[inline]
25    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
26        write!(f, "{{ ")?;
27
28        let num_elements = self.len();
29
30        for (idx, (key, value)) in self.iter().enumerate() {
31            write!(f, "{}: {:?}", key, value)?;
32
33            if idx + 1 < num_elements {
34                write!(f, ", ")?;
35            }
36        }
37
38        write!(f, " }}")?;
39
40        Ok(())
41    }
42}
43
44impl Dictionary {
45    /// Returns a reference to the value corresponding to the key.
46    #[inline]
47    pub fn get<Q>(&self, query: &Q) -> Option<&Object>
48    where
49        Q: ?Sized + PartialEq<crate::String>,
50    {
51        self.iter().find_map(|(key, value)| (query == key).then_some(value))
52    }
53
54    /// Returns a mutable reference to the value corresponding to the key.
55    #[inline]
56    pub fn get_mut<Q>(&mut self, query: &Q) -> Option<&mut Object>
57    where
58        Q: ?Sized + PartialEq<crate::String>,
59    {
60        self.iter_mut()
61            .find_map(|(key, value)| (query == key).then_some(value))
62    }
63
64    /// Returns `true` if the dictionary contains no elements.
65    #[inline]
66    pub fn is_empty(&self) -> bool {
67        self.0.is_empty()
68    }
69
70    /// Returns an iterator over the `(String, Object)` pairs of the
71    /// dictionary.
72    #[inline]
73    pub fn iter(&self) -> DictIter<'_> {
74        DictIter(self.0.iter())
75    }
76
77    /// Returns a mutable iterator over the `(String, Object)` pairs of the
78    /// dictionary.
79    #[inline]
80    pub fn iter_mut(&mut self) -> DictIterMut<'_> {
81        DictIterMut(self.0.iter_mut())
82    }
83
84    /// Returns the number of elements in the dictionary.
85    #[inline]
86    pub fn len(&self) -> usize {
87        self.0.len()
88    }
89
90    /// Creates a new, empty `Dictionary`.
91    #[inline]
92    pub fn new() -> Self {
93        Self(KVec::new())
94    }
95
96    /// Returns a non-owning version of this `Array`.
97    #[inline]
98    pub fn non_owning(&self) -> NonOwning<'_, Self> {
99        #[allow(clippy::unnecessary_struct_initialization)]
100        NonOwning::new(Self(KVec { ..self.0 }))
101    }
102}
103
104impl<S> core::ops::Index<S> for Dictionary
105where
106    S: PartialEq<crate::String>,
107{
108    type Output = Object;
109
110    #[inline]
111    fn index(&self, index: S) -> &Self::Output {
112        self.get(&index).unwrap()
113    }
114}
115
116impl<S> core::ops::IndexMut<S> for Dictionary
117where
118    S: PartialEq<crate::String>,
119{
120    #[inline]
121    fn index_mut(&mut self, index: S) -> &mut Self::Output {
122        self.get_mut(&index).unwrap()
123    }
124}
125
126impl<K, V> FromIterator<(K, V)> for Dictionary
127where
128    K: Into<crate::String>,
129    V: Into<Object>,
130{
131    #[inline]
132    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
133        Self(
134            iter.into_iter()
135                .filter_map(|(k, v)| {
136                    let value = v.into();
137                    value
138                        .is_some()
139                        .then(|| KeyValuePair { key: k.into(), value })
140                })
141                .collect(),
142        )
143    }
144}
145
146impl IntoIterator for Dictionary {
147    type Item = (crate::String, Object);
148    type IntoIter = DictIterator;
149
150    #[inline]
151    fn into_iter(self) -> Self::IntoIter {
152        DictIterator(self.0.into_iter())
153    }
154}
155
156/// An owning iterator over the `(String, Object)` pairs of a [`Dictionary`].
157#[derive(Clone)]
158pub struct DictIterator(kvec::IntoIter<KeyValuePair>);
159
160impl Iterator for DictIterator {
161    type Item = (crate::String, Object);
162
163    #[inline]
164    fn next(&mut self) -> Option<Self::Item> {
165        self.0.next().map(|pair| (pair.key, pair.value))
166    }
167
168    #[inline]
169    fn size_hint(&self) -> (usize, Option<usize>) {
170        self.0.size_hint()
171    }
172}
173
174impl ExactSizeIterator for DictIterator {
175    #[inline]
176    fn len(&self) -> usize {
177        self.0.len()
178    }
179}
180
181impl DoubleEndedIterator for DictIterator {
182    #[inline]
183    fn next_back(&mut self) -> Option<Self::Item> {
184        self.0.next_back().map(|pair| (pair.key, pair.value))
185    }
186}
187
188impl core::iter::FusedIterator for DictIterator {}
189
190/// An iterator over the `(String, Object)` pairs of a [`Dictionary`].
191#[derive(Clone)]
192pub struct DictIter<'a>(core::slice::Iter<'a, KeyValuePair>);
193
194impl<'a> Iterator for DictIter<'a> {
195    type Item = (&'a crate::String, &'a Object);
196
197    #[inline]
198    fn next(&mut self) -> Option<Self::Item> {
199        self.0.next().map(|pair| (&pair.key, &pair.value))
200    }
201
202    #[inline]
203    fn size_hint(&self) -> (usize, Option<usize>) {
204        self.0.size_hint()
205    }
206}
207
208impl ExactSizeIterator for DictIter<'_> {
209    #[inline]
210    fn len(&self) -> usize {
211        self.0.len()
212    }
213}
214
215impl DoubleEndedIterator for DictIter<'_> {
216    #[inline]
217    fn next_back(&mut self) -> Option<Self::Item> {
218        self.0.next_back().map(|pair| (&pair.key, &pair.value))
219    }
220}
221
222impl core::iter::FusedIterator for DictIter<'_> {}
223
224/// A mutable iterator over the `(String, Object)` pairs of a [`Dictionary`].
225pub struct DictIterMut<'a>(core::slice::IterMut<'a, KeyValuePair>);
226
227impl<'a> Iterator for DictIterMut<'a> {
228    type Item = (&'a mut crate::String, &'a mut Object);
229
230    #[inline]
231    fn next(&mut self) -> Option<Self::Item> {
232        self.0.next().map(|pair| (&mut pair.key, &mut pair.value))
233    }
234
235    #[inline]
236    fn size_hint(&self) -> (usize, Option<usize>) {
237        self.0.size_hint()
238    }
239}
240
241impl ExactSizeIterator for DictIterMut<'_> {
242    #[inline]
243    fn len(&self) -> usize {
244        self.0.len()
245    }
246}
247
248impl DoubleEndedIterator for DictIterMut<'_> {
249    #[inline]
250    fn next_back(&mut self) -> Option<Self::Item> {
251        self.0.next_back().map(|pair| (&mut pair.key, &mut pair.value))
252    }
253}
254
255impl core::iter::FusedIterator for DictIterMut<'_> {}
256
257impl lua::Poppable for Dictionary {
258    #[inline]
259    unsafe fn pop(
260        lstate: *mut lua::ffi::lua_State,
261    ) -> Result<Self, lua::Error> {
262        use lua::ffi::*;
263
264        if lua_gettop(lstate) == 0 {
265            return Err(lua::Error::PopEmptyStack);
266        } else if lua_type(lstate, -1) != LUA_TTABLE {
267            let ty = lua_type(lstate, -1);
268            return Err(lua::Error::pop_wrong_type::<Self>(LUA_TTABLE, ty));
269        }
270
271        let mut kvec = KVec::with_capacity(lua_objlen(lstate, -1));
272
273        lua_pushnil(lstate);
274
275        while lua_next(lstate, -2) != 0 {
276            let value = Object::pop(lstate)?;
277
278            // The following `String::pop()` will pop the key, so we push
279            // another copy on the stack for the next iteration.
280            lua_pushvalue(lstate, -1);
281
282            let key = crate::String::pop(lstate)?;
283
284            kvec.push(KeyValuePair { key, value });
285        }
286
287        // Pop the table.
288        lua_pop(lstate, 1);
289
290        Ok(Self(kvec))
291    }
292}
293
294impl lua::Pushable for Dictionary {
295    #[inline]
296    unsafe fn push(
297        self,
298        lstate: *mut lua::ffi::lua_State,
299    ) -> Result<core::ffi::c_int, lua::Error> {
300        use lua::ffi::*;
301
302        lua_createtable(lstate, 0, self.len() as _);
303
304        for (key, obj) in self {
305            lua_pushlstring(lstate, key.as_ptr(), key.len());
306            obj.push(lstate)?;
307            lua_rawset(lstate, -3);
308        }
309
310        Ok(1)
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use crate::{Object, String as NvimString};
318
319    #[test]
320    fn dict_layout() {
321        use core::alloc::Layout;
322
323        assert_eq!(
324            Layout::new::<Dictionary>(),
325            Layout::new::<KVec<KeyValuePair>>()
326        );
327    }
328
329    #[test]
330    fn iter_basic() {
331        let dict = Dictionary::from_iter([
332            ("foo", "Foo"),
333            ("bar", "Bar"),
334            ("baz", "Baz"),
335        ]);
336
337        let mut iter = dict.into_iter();
338        assert_eq!(
339            Some((NvimString::from("foo"), Object::from("Foo"))),
340            iter.next()
341        );
342        assert_eq!(
343            Some((NvimString::from("bar"), Object::from("Bar"))),
344            iter.next()
345        );
346        assert_eq!(
347            Some((NvimString::from("baz"), Object::from("Baz"))),
348            iter.next()
349        );
350        assert_eq!(None, iter.next());
351    }
352
353    #[test]
354    fn drop_iter_halfway() {
355        let dict = Dictionary::from_iter([
356            ("foo", "Foo"),
357            ("bar", "Bar"),
358            ("baz", "Baz"),
359        ]);
360
361        let mut iter = dict.into_iter();
362        assert_eq!(
363            Some((NvimString::from("foo"), Object::from("Foo"))),
364            iter.next()
365        );
366    }
367}