1use std::collections::HashMap;
2use std::ffi::c_int;
3use std::mem::ManuallyDrop;
4use std::{fmt, ptr};
5
6use luajit_bindings::{self as lua, ffi::*, Poppable, Pushable};
7
8use super::{KVec, Object, String};
9
10pub type Dictionary = KVec<KeyValuePair>;
14
15#[derive(Clone, PartialEq)]
19#[repr(C)]
20pub struct KeyValuePair {
21 pub(crate) key: String,
22 pub(crate) value: Object,
23}
24
25impl fmt::Debug for KeyValuePair {
26 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 fmt::Display::fmt(self, f)
28 }
29}
30
31impl fmt::Display for KeyValuePair {
32 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33 write!(f, "{}: {}", self.key, self.value)
34 }
35}
36
37impl<K, V> From<(K, V)> for KeyValuePair
38where
39 K: Into<String>,
40 V: Into<Object>,
41{
42 fn from((k, v): (K, V)) -> Self {
43 Self { key: k.into(), value: v.into() }
44 }
45}
46
47impl Dictionary {
48 pub fn get<Q>(&self, query: &Q) -> Option<&Object>
49 where
50 String: PartialEq<Q>,
51 {
52 self.iter()
53 .find_map(|pair| (&pair.key == query).then_some(&pair.value))
54 }
55
56 pub fn get_mut<Q>(&mut self, query: &Q) -> Option<&mut Object>
57 where
58 String: PartialEq<Q>,
59 {
60 self.iter_mut()
61 .find_map(|pair| (&pair.key == query).then_some(&mut pair.value))
62 }
63}
64
65impl fmt::Debug for Dictionary {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 f.debug_map()
68 .entries(self.iter().map(|pair| (&pair.key, &pair.value)))
69 .finish()
70 }
71}
72
73impl fmt::Display for Dictionary {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 fmt::Debug::fmt(self, f)
76 }
77}
78
79impl<S: Into<String>> std::ops::Index<S> for Dictionary {
80 type Output = Object;
81
82 fn index(&self, index: S) -> &Self::Output {
83 self.get(&index.into()).unwrap()
84 }
85}
86
87impl<S: Into<String>> std::ops::IndexMut<S> for Dictionary {
88 fn index_mut(&mut self, index: S) -> &mut Self::Output {
89 self.get_mut(&index.into()).unwrap()
90 }
91}
92
93impl Pushable for Dictionary {
94 unsafe fn push(self, lstate: *mut lua_State) -> Result<c_int, lua::Error> {
95 lua::ffi::lua_createtable(lstate, 0, self.len() as _);
96
97 for (key, obj) in self {
98 lua::ffi::lua_pushlstring(lstate, key.as_ptr(), key.len());
99 obj.push(lstate)?;
100 lua::ffi::lua_rawset(lstate, -3);
101 }
102
103 Ok(1)
104 }
105}
106
107impl Poppable for Dictionary {
108 unsafe fn pop(lstate: *mut lua_State) -> Result<Self, lua::Error> {
109 <HashMap<crate::String, Object>>::pop(lstate).map(Into::into)
110 }
111}
112
113impl IntoIterator for Dictionary {
114 type IntoIter = DictIterator;
115 type Item = <DictIterator as Iterator>::Item;
116
117 #[inline]
118 fn into_iter(self) -> Self::IntoIter {
119 let arr = ManuallyDrop::new(self);
121 let start = arr.items;
122 let end = unsafe { start.add(arr.len()) };
123
124 DictIterator { start, end }
125 }
126}
127
128pub struct DictIterator {
131 start: *const KeyValuePair,
132 end: *const KeyValuePair,
133}
134
135impl Iterator for DictIterator {
136 type Item = (String, Object);
137
138 #[inline]
139 fn next(&mut self) -> Option<Self::Item> {
140 if self.start == self.end {
141 return None;
142 }
143 let current = self.start;
144 self.start = unsafe { self.start.offset(1) };
145 let KeyValuePair { key, value } = unsafe { ptr::read(current) };
146 Some((key, value))
147 }
148
149 #[inline]
150 fn size_hint(&self) -> (usize, Option<usize>) {
151 let exact = self.len();
152 (exact, Some(exact))
153 }
154
155 #[inline]
156 fn count(self) -> usize {
157 self.len()
158 }
159}
160
161impl ExactSizeIterator for DictIterator {
162 #[inline]
163 fn len(&self) -> usize {
164 unsafe { self.end.offset_from(self.start) as usize }
165 }
166}
167
168impl DoubleEndedIterator for DictIterator {
169 #[inline]
170 fn next_back(&mut self) -> Option<Self::Item> {
171 if self.start == self.end {
172 return None;
173 }
174 let current = self.end;
175 self.end = unsafe { self.end.offset(-1) };
176 let KeyValuePair { key, value } = unsafe { ptr::read(current) };
177 Some((key, value))
178 }
179}
180
181impl std::iter::FusedIterator for DictIterator {}
182
183impl Drop for DictIterator {
184 fn drop(&mut self) {
185 while self.start != self.end {
186 unsafe {
187 ptr::drop_in_place(self.start as *mut Object);
188 self.start = self.start.offset(1);
189 }
190 }
191 }
192}
193
194impl<K, V> FromIterator<(K, V)> for Dictionary
195where
196 K: Into<String>,
197 V: Into<Object>,
198{
199 fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
200 iter.into_iter()
201 .map(|(k, v)| (k, v.into()))
202 .filter(|(_, obj)| obj.is_some())
203 .map(KeyValuePair::from)
204 .collect::<Vec<KeyValuePair>>()
205 .into()
206 }
207}
208
209impl<K, V> From<HashMap<K, V>> for Dictionary
210where
211 String: From<K>,
212 Object: From<V>,
213{
214 fn from(hashmap: HashMap<K, V>) -> Self {
215 hashmap.into_iter().collect()
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::{Dictionary, Object, String as NvimString};
222
223 #[test]
224 fn iter_basic() {
225 let dict = Dictionary::from_iter([
226 ("foo", "Foo"),
227 ("bar", "Bar"),
228 ("baz", "Baz"),
229 ]);
230
231 let mut iter = dict.into_iter();
232 assert_eq!(
233 Some((NvimString::from("foo"), Object::from("Foo"))),
234 iter.next()
235 );
236 assert_eq!(
237 Some((NvimString::from("bar"), Object::from("Bar"))),
238 iter.next()
239 );
240 assert_eq!(
241 Some((NvimString::from("baz"), Object::from("Baz"))),
242 iter.next()
243 );
244 assert_eq!(None, iter.next());
245 }
246
247 #[test]
248 fn drop_iter_halfway() {
249 let dict = Dictionary::from_iter([
250 ("foo", "Foo"),
251 ("bar", "Bar"),
252 ("baz", "Baz"),
253 ]);
254
255 let mut iter = dict.into_iter();
256 assert_eq!(
257 Some((NvimString::from("foo"), Object::from("Foo"))),
258 iter.next()
259 );
260 }
261
262 #[test]
263 fn debug_dict() {
264 let dict = Dictionary::from_iter([
265 ("a", Object::from(1)),
266 ("b", Object::from(true)),
267 ("c", Object::from("foobar")),
268 ]);
269
270 assert_eq!(
271 String::from("{a: 1, b: true, c: \"foobar\"}"),
272 format!("{dict}")
273 );
274 }
275
276 #[test]
277 fn debug_nested_dict() {
278 let dict = Dictionary::from_iter([(
279 "foo",
280 Object::from(Dictionary::from_iter([("a", 1)])),
281 )]);
282
283 assert_eq!(String::from("{foo: {a: 1}}"), format!("{dict}"));
284 }
285}