1use oxi_luajit as lua;
2
3use crate::kvec::{self, KVec};
4use crate::NonOwning;
5use crate::Object;
6
7#[derive(Clone, Default, PartialEq)]
10#[repr(transparent)]
11pub struct Dictionary(pub(super) KVec<KeyValuePair>);
12
13#[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 #[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 #[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 #[inline]
66 pub fn is_empty(&self) -> bool {
67 self.0.is_empty()
68 }
69
70 #[inline]
73 pub fn iter(&self) -> DictIter<'_> {
74 DictIter(self.0.iter())
75 }
76
77 #[inline]
80 pub fn iter_mut(&mut self) -> DictIterMut<'_> {
81 DictIterMut(self.0.iter_mut())
82 }
83
84 #[inline]
86 pub fn len(&self) -> usize {
87 self.0.len()
88 }
89
90 #[inline]
92 pub fn new() -> Self {
93 Self(KVec::new())
94 }
95
96 #[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#[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#[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
224pub 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 lua_pushvalue(lstate, -1);
281
282 let key = crate::String::pop(lstate)?;
283
284 kvec.push(KeyValuePair { key, value });
285 }
286
287 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}