1 /**
2     DataNode Key-Value Pair Abstraction
3 
4     Copyright: 
5         Copyright © 2020-2026, Inochi2D Project
6     
7     License:
8         $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License)
9     
10     Authors:
11         Luna Nielsen
12 */
13 module inp.format.dict;
14 import nulib.collections.internal.marray;
15 import numem.core.traits;
16 import numem.rc;
17 import numem;
18 
19 /**
20     An ordered refcounted dictionary.
21 */
22 struct RcOrderedDictionary(TKey, TValue) {
23 private:
24 @nogc:
25     alias KVT = KV!(TKey, TValue);
26     alias KVStoreT = ManagedArray!(KV!(TKey, TValue));
27     Rc!KVStoreT values;
28 
29     /// Helper that finds an entry by its key.
30     pragma(inline, true)
31     ptrdiff_t findEntry()(auto ref inout(TKey) key) inout nothrow {
32         if (!values) return -1;
33 
34         foreach(i, ref entry; values) {
35             if (entry.key == key)
36                 return i;
37         }
38         return -1;
39     }
40 
41 public:
42 
43     /**
44         Makes a new ordered dictionary.
45     */
46     static typeof(this) make() {
47         typeof(this) result;
48         result.values = Rc!(KVStoreT)(KVStoreT());
49         return result;
50     }
51 
52     /// Destructor
53     ~this() nothrow @trusted {
54         if (values) {
55             values.release();
56         }
57     }
58 
59     /**
60         Copy-constructor
61     */
62     this()(auto ref return scope inout(typeof(this)) rhs) pure nothrow @trusted {
63         nu_memmove(&values, &rhs.values, typeof(values).sizeof);
64         if (values)
65             values.retain();
66     }
67 
68     /**
69         Length of the node.
70     */
71     @property size_t length() pure => values ? values.length : 0;
72 
73     /**
74         Assignment operator
75     */
76     void opAssign()(auto ref return scope inout(typeof(this)) rhs) {
77         if (values)
78             values.release();
79         
80         nu_memmove(&values, &rhs.values, typeof(values).sizeof);
81         if (values)
82             values.retain();
83     }
84 
85     /**
86         Removes the given key from the object.
87 
88         Params:
89             key = The key to remove.
90     */
91     void remove()(auto ref TKey key) {
92         ptrdiff_t idx = findEntry(key);
93         if (idx >= 0) {
94             values.deleteRange(values[idx..idx+1]);
95         }
96     }
97 
98     /**
99         Assigns an element of the dictionary.
100 
101         Params:
102             key =   The key to query.
103             value = The value to set.
104     */
105     void opIndexAssign()(auto ref TValue value, auto ref TKey key) {
106         if (!values)
107             this.values = Rc!(KVStoreT)(KVStoreT());
108 
109         ptrdiff_t idx = findEntry(key);
110         if (idx >= 0) {
111             values[idx] = KV!(TKey, TValue)(key, value);
112             return;
113         }
114 
115         // Append our new entry.
116         values.value.resize(values.length+1);
117         values[$-1] = KV!(TKey, TValue)(key, value);
118     }
119 
120     /**
121         Gets whether the given key is present in the object.
122 
123         Params:
124             key = The key to query.
125 
126         Returns:
127             $(D true) if the object contains a value with the given key,
128             $(D false) otherwise.
129     */
130     inout(TValue)* opBinaryRight(string op)(auto ref TKey key) inout nothrow
131     if (op == "in") {
132         inout idx = findEntry(key);
133         return idx != -1 ? &(values[idx].value) : null;
134     }
135 
136     /**
137         Gets the given entry in the object.
138 
139         Params:
140             key = The key to query.
141 
142         Returns:
143             The $(D DataNode) with the given key.
144     */
145     ref TValue opIndex()(auto ref TKey key) {
146         ptrdiff_t idx = findEntry(key);
147         assert(idx >= 0);
148 
149         return values[idx].value;
150     }
151 
152     /**
153         Dict-iterator
154     */
155     int opApply(scope int delegate(size_t i, ref TKey key, ref TValue value) dg) {
156         if (!values)
157             return 0;
158         
159         auto dgf = cast(int delegate(size_t i, ref TKey key, ref TValue value) @nogc scope)dg;
160         foreach (i; 0..values.length) {
161             int result = dgf(i, values[i].key, values[i].value);
162             if (result)
163                 return result;
164         }
165         return 0;
166     }
167 
168     /**
169         Dict-iterator
170     */
171     int opApply(scope int delegate(ref TKey key, ref TValue value) dg) {
172         if (!values)
173             return 0;
174         
175         auto dgf = cast(int delegate(ref TKey key, ref TValue value) @nogc scope)dg;
176         foreach (i; 0..values.length) {
177             int result = dgf(values[i].key, values[i].value);
178             if (result)
179                 return result;
180         }
181         return 0;
182     }
183 
184     /**
185         Dict-iterator
186     */
187     int opApply(scope int delegate(ref TValue value) dg) {
188         if (!values)
189             return 0;
190 
191         auto dgf = cast(int delegate(ref TValue value) @nogc scope)dg;
192         foreach (i; 0..values.length) {
193             int result = dgf(values[i].value);
194             if (result)
195                 return result;
196         }
197         return 0;
198     }
199 }
200 
201 
202 //
203 //              IMPLEMENTATION DETAILS
204 //
205 private:
206 
207 /**
208     DataNode Key-Value Pair.
209 */
210 struct KV(TKey, TValue) {
211 @nogc:
212     TKey key;
213     TValue value;
214 
215     pragma(inline, true)
216     static void kvassign(T)(ref T dst, ref T src) {
217         static if (is(T == string)) {
218             dst = src.nu_dup();
219         } else static if (is(T == U[], U)) {
220             dst = src.nu_dup();
221         } else static if (hasElaborateMove!T) {
222             dst = src.move();
223         } else static if (hasElaborateCopyConstructor!T) {
224             dst = T(src);
225         } else {
226             dst = cast(T)src;
227         }
228     }
229 
230     pragma(inline, true)
231     static void kvdelete(T)(ref T dst) {
232         static if (hasElaborateDestructor!T) {
233             nogc_trydelete(dst);
234         } else static if (is(T == U[], U)) {
235             nu_freea(dst);
236         } else {
237             nogc_initialize(dst);
238         }
239     }
240 
241     ~this() nothrow {
242         kvdelete(key);
243         kvdelete(value);
244     }
245 
246     this()(auto ref TKey key, auto ref TValue value) @trusted {
247         kvassign!TKey(this.key, key);
248         kvassign!TValue(this.value, value);
249     }
250 
251     /// Copy-constructor
252     this(ref return scope inout(typeof(this)) rhs) @trusted {
253         kvassign!TKey(this.key, cast(TKey)rhs.key);
254         kvassign!TValue(this.value, cast(TValue)rhs.value);
255     }
256 }