File Coverage

OrderedHash.xs
Criterion Covered Total %
statement 129 137 94.1
branch 92 130 70.7
condition n/a
subroutine n/a
pod n/a
total 221 267 82.7


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2              
3             #include "EXTERN.h"
4             #include "perl.h"
5             #include "XSUB.h"
6              
7             #include "ppport.h"
8             #include "tie_orderedhash.h"
9             #include "oh_core.h"
10              
11             /* ---- helpers shared between XSUBs ----------------------------- */
12              
13             /* Trusted-fast accessors for the FIRSTKEY/NEXTKEY hot path. */
14             PERL_STATIC_INLINE AV *
15 20225           xs_keys_av_fast(pTHX_ SV *self)
16             {
17 20225           AV *av = (AV *)SvRV(self);
18 20225           return (AV *)SvRV(AvARRAY(av)[1]);
19             }
20              
21             PERL_STATIC_INLINE SV *
22 20225           xs_cursor_sv_fast(pTHX_ SV *self)
23             {
24 20225           AV *av = (AV *)SvRV(self);
25 20225           return AvARRAY(av)[3];
26             }
27              
28             MODULE = Tie::OrderedHash PACKAGE = Tie::OrderedHash PREFIX = oh_
29              
30             PROTOTYPES: DISABLE
31              
32             # ---- tied-hash interface ------------------------------------------
33              
34             # TIEHASH(class, ...) -- construct fresh, then Push any (k,v) pairs.
35             SV *
36             oh_TIEHASH(class, ...)
37             const char *class
38             PREINIT:
39             SV *self;
40             int i;
41             CODE:
42             PERL_UNUSED_VAR(class);
43 44           self = tie_oh_new(aTHX);
44 44 100         if ((items - 1) % 2 != 0) {
45 3           SvREFCNT_dec(self);
46 3           croak("Tie::OrderedHash::TIEHASH: odd number of arguments");
47             }
48 61 100         for (i = 1; i + 1 < items; i += 2) {
49             STRLEN klen;
50 20           const char *key = SvPV(ST(i), klen);
51 20           SV *val = newSVsv(ST(i + 1));
52 20           tie_oh_store(aTHX_ self, key, klen, val);
53             }
54 41           RETVAL = self;
55             OUTPUT:
56             RETVAL
57              
58             SV *
59             oh_FETCH(self, key)
60             SV *self
61             SV *key
62             PREINIT:
63             HV *idx; AV *keys; AV *vals;
64             HE *he;
65             SV **vslot;
66             SSize_t pos;
67             CODE:
68             /* Fast-path FETCH: bypass the public-ABI wrapper, walk the AV
69             * directly. Share the internal value SV (SvREFCNT_inc rather
70             * than newSVsv) so `$h{a}->{nested} = 1` autoviv lands back in
71             * our storage - matches both Tie::IxHash and plain-HV semantics. */
72 92           oh_resolve_fast(aTHX_ self, &idx, &keys, &vals);
73 92           he = hv_fetch_ent(idx, key, 0, 0);
74 92 100         if (!he) {
75 7           RETVAL = newSV(0);
76             } else {
77 85           pos = (SSize_t)SvIV(HeVAL(he));
78 85           vslot = av_fetch(vals, pos, 0);
79 85 50         if (vslot && *vslot) {
    50          
80 85           RETVAL = *vslot;
81 85           SvREFCNT_inc_simple_void_NN(RETVAL);
82             } else {
83 0           RETVAL = newSV(0);
84             }
85             }
86             OUTPUT:
87             RETVAL
88              
89             void
90             oh_STORE(self, key, value)
91             SV *self
92             SV *key
93             SV *value
94             PREINIT:
95             HV *idx; AV *keys; AV *vals;
96             HE *he;
97             SV *vcopy;
98             CODE:
99 10086           oh_resolve_fast(aTHX_ self, &idx, &keys, &vals);
100             /* hv_fetch_ent with our input key SV reuses any cached hash. */
101 10086           he = hv_fetch_ent(idx, key, 0, 0);
102 10086 100         if (he) {
103             /* Existing key: overwrite the value SV in place if we can.
104             * sv_setsv on the existing slot avoids a fresh SV alloc per
105             * overwrite - meaningful in update-heavy workloads. Falls
106             * back to a fresh av_store if the slot SV has any unusual
107             * state (eg readonly, frozen). */
108 9           SSize_t pos = (SSize_t)SvIV(HeVAL(he));
109 9           SV **vslot = av_fetch(vals, pos, 0);
110 9 50         if (vslot && *vslot && !SvREADONLY(*vslot)) {
    50          
    50          
111 9           sv_setsv(*vslot, value);
112             } else {
113 0           vcopy = newSVsv(value);
114 0 0         if (!av_store(vals, pos, vcopy))
115 0           SvREFCNT_dec(vcopy);
116             }
117             } else {
118             /* New key: append, then record index in idx HV. */
119             STRLEN klen;
120 10077           const char *kpv = SvPV(key, klen);
121 10077           SV *key_sv = newSVpvn(kpv, klen);
122             SV *idx_sv;
123             SSize_t newpos;
124 10077           vcopy = newSVsv(value);
125 10077           av_push(keys, key_sv);
126 10077           newpos = av_len(keys);
127 10077 50         if (!av_store(vals, newpos, vcopy))
128 0           SvREFCNT_dec(vcopy);
129 10077           idx_sv = newSViv((IV)newpos);
130 10077 50         if (!hv_store_ent(idx, key_sv, idx_sv, 0))
131 0           SvREFCNT_dec(idx_sv);
132             }
133              
134             int
135             oh_EXISTS(self, key)
136             SV *self
137             SV *key
138             PREINIT:
139             HV *idx; AV *keys; AV *vals;
140             CODE:
141 17           oh_resolve_fast(aTHX_ self, &idx, &keys, &vals);
142 17 50         RETVAL = hv_exists_ent(idx, key, 0) ? 1 : 0;
143             OUTPUT:
144             RETVAL
145              
146             SV *
147             oh_DELETE(self, key)
148             SV *self
149             SV *key
150             PREINIT:
151             STRLEN klen;
152             const char *kpv;
153             SV *got;
154             CODE:
155 20           kpv = SvPV(key, klen);
156 20           got = tie_oh_delete(aTHX_ self, kpv, klen);
157 20 100         RETVAL = got ? newSVsv(got) : newSV(0);
158             OUTPUT:
159             RETVAL
160              
161             void
162             oh_CLEAR(self)
163             SV *self
164             CODE:
165 2           tie_oh_clear(aTHX_ self);
166              
167             SV *
168             oh_FIRSTKEY(self)
169             SV *self
170             PREINIT:
171             AV *keys;
172             SV *cursor;
173             SV **kslot;
174             CODE:
175 82           keys = xs_keys_av_fast(aTHX_ self);
176 82           cursor = xs_cursor_sv_fast(aTHX_ self);
177 82 100         if (av_len(keys) < 0) {
178 25           sv_setiv(cursor, 0);
179 25           XSRETURN_UNDEF;
180             }
181 57           kslot = av_fetch(keys, 0, 0);
182 57 50         if (!kslot || !*kslot) {
    50          
183 0           sv_setiv(cursor, 0);
184 0           XSRETURN_UNDEF;
185             }
186 57           sv_setiv(cursor, 1);
187 57           SvREFCNT_inc_simple_void_NN(*kslot);
188 57           RETVAL = *kslot;
189             OUTPUT:
190             RETVAL
191              
192             SV *
193             oh_NEXTKEY(self, lastkey)
194             SV *self
195             SV *lastkey
196             PREINIT:
197             AV *keys;
198             SV *cursor;
199             SSize_t pos;
200             SV **kslot;
201             CODE:
202             PERL_UNUSED_VAR(lastkey);
203 20143           keys = xs_keys_av_fast(aTHX_ self);
204 20143           cursor = xs_cursor_sv_fast(aTHX_ self);
205 20143           pos = (SSize_t)SvIV(cursor);
206 20143 50         if (pos < 0 || pos > av_len(keys)) XSRETURN_UNDEF;
    100          
207 20092           kslot = av_fetch(keys, pos, 0);
208 20092 50         if (!kslot || !*kslot) XSRETURN_UNDEF;
    50          
209 20092           sv_setiv(cursor, pos + 1);
210 20092           SvREFCNT_inc_simple_void_NN(*kslot);
211 20092           RETVAL = *kslot;
212             OUTPUT:
213             RETVAL
214              
215             int
216             oh_SCALAR(self)
217             SV *self
218             CODE:
219 9 50         RETVAL = tie_oh_count(aTHX_ self) > 0 ? 1 : 0;
220             OUTPUT:
221             RETVAL
222              
223             # ---- OO interface (class methods) ---------------------------------
224              
225             SV *
226             oh_new(class, ...)
227             const char *class
228             PREINIT:
229             SV *self;
230             int i;
231             CODE:
232             PERL_UNUSED_VAR(class);
233 4           self = tie_oh_new(aTHX);
234 4 100         if ((items - 1) % 2 != 0) {
235 1           SvREFCNT_dec(self);
236 1           croak("Tie::OrderedHash::new: odd number of arguments");
237             }
238 8 100         for (i = 1; i + 1 < items; i += 2) {
239             STRLEN klen;
240 5           const char *key = SvPV(ST(i), klen);
241 5           SV *val = newSVsv(ST(i + 1));
242 5           tie_oh_store(aTHX_ self, key, klen, val);
243             }
244 3           RETVAL = self;
245             OUTPUT:
246             RETVAL
247              
248             # Push -- in-order insert/update of (k,v) pairs. Returns post-count.
249             int
250             oh_Push(self, ...)
251             SV *self
252             PREINIT:
253             int i;
254             CODE:
255 5 100         if ((items - 1) % 2 != 0)
256 1           croak("Tie::OrderedHash::Push: odd number of arguments");
257 9 100         for (i = 1; i + 1 < items; i += 2) {
258             STRLEN klen;
259 5           const char *key = SvPV(ST(i), klen);
260 5           SV *val = newSVsv(ST(i + 1));
261 5           tie_oh_store(aTHX_ self, key, klen, val);
262             }
263 4 50         RETVAL = (int)tie_oh_count(aTHX_ self);
264             OUTPUT:
265             RETVAL
266              
267             # Pop -- remove last pair, return (key, value). Empty list on empty.
268             void
269             oh_Pop(self)
270             SV *self
271             PREINIT:
272             SV *kpop, *vpop;
273             PPCODE:
274 10           vpop = oh_pop(aTHX_ self, &kpop);
275 10 100         if (!kpop || !vpop) XSRETURN_EMPTY;
    50          
276 4 50         EXTEND(SP, 2);
277 4           PUSHs(kpop);
278 4           PUSHs(vpop);
279              
280             # Shift -- same but front.
281             void
282             oh_Shift(self)
283             SV *self
284             PREINIT:
285             SV *kshift, *vshift;
286             PPCODE:
287 10           vshift = oh_shift(aTHX_ self, &kshift);
288 10 100         if (!kshift || !vshift) XSRETURN_EMPTY;
    50          
289 4 50         EXTEND(SP, 2);
290 4           PUSHs(kshift);
291 4           PUSHs(vshift);
292              
293             # Unshift -- prepend (k,v) pairs. Per Tie::IxHash, existing keys
294             # are updated in place without changing position.
295             int
296             oh_Unshift(self, ...)
297             SV *self
298             PREINIT:
299             int i;
300             CODE:
301 3 100         if ((items - 1) % 2 != 0)
302 1           croak("Tie::OrderedHash::Unshift: odd number of arguments");
303             /* Walk the trailing args in REVERSE so the supplied list ends
304             * up in source order at the front of the hash. Tie::IxHash's
305             * Unshift documents this behaviour. */
306 5 100         for (i = items - 2; i >= 1; i -= 2) {
307 3           SV *val = newSVsv(ST(i + 1));
308 3           oh_unshift_pair(aTHX_ self, ST(i), val);
309             }
310 2 50         RETVAL = (int)tie_oh_count(aTHX_ self);
311             OUTPUT:
312             RETVAL
313              
314             # Keys: with no args, return the whole keys list in order. With
315             # args, return keys at those indices (negative offsets supported).
316             void
317             oh_Keys(self, ...)
318             SV *self
319             PREINIT:
320             HV *idx; AV *keys; AV *vals;
321             SSize_t i, n;
322             PPCODE:
323 32           oh_resolve(aTHX_ self, &idx, &keys, &vals);
324 32           n = av_len(keys) + 1;
325 32 100         if (items == 1) {
326 13 50         EXTEND(SP, n);
    50          
327 38 100         for (i = 0; i < n; i++) {
328 25           SV **slot = av_fetch(keys, i, 0);
329 25 50         PUSHs(slot && *slot ? sv_mortalcopy(*slot) : &PL_sv_undef);
    50          
330             }
331             } else {
332             int j;
333 19 50         EXTEND(SP, items - 1);
    50          
334 48 100         for (j = 1; j < items; j++) {
335 29           IV want = SvIV(ST(j));
336 29 100         SSize_t pos = want < 0 ? n + want : want;
337 29 100         if (pos < 0 || pos >= n) {
    100          
338 8           PUSHs(&PL_sv_undef);
339             } else {
340 21           SV **slot = av_fetch(keys, pos, 0);
341 21 50         PUSHs(slot && *slot ? sv_mortalcopy(*slot) : &PL_sv_undef);
    50          
342             }
343             }
344             }
345              
346             void
347             oh_Values(self, ...)
348             SV *self
349             PREINIT:
350             HV *idx; AV *keys; AV *vals;
351             SSize_t i, n;
352             PPCODE:
353 24           oh_resolve(aTHX_ self, &idx, &keys, &vals);
354 24           n = av_len(vals) + 1;
355 24 100         if (items == 1) {
356 11 50         EXTEND(SP, n);
    50          
357 33 100         for (i = 0; i < n; i++) {
358 22           SV **slot = av_fetch(vals, i, 0);
359 22 50         PUSHs(slot && *slot ? sv_mortalcopy(*slot) : &PL_sv_undef);
    50          
360             }
361             } else {
362             int j;
363 13 50         EXTEND(SP, items - 1);
    50          
364 31 100         for (j = 1; j < items; j++) {
365 18           IV want = SvIV(ST(j));
366 18 100         SSize_t pos = want < 0 ? n + want : want;
367 18 100         if (pos < 0 || pos >= n) {
    100          
368 3           PUSHs(&PL_sv_undef);
369             } else {
370 15           SV **slot = av_fetch(vals, pos, 0);
371 15 50         PUSHs(slot && *slot ? sv_mortalcopy(*slot) : &PL_sv_undef);
    50          
372             }
373             }
374             }
375              
376             int
377             oh_Length(self)
378             SV *self
379             CODE:
380 19 100         RETVAL = (int)tie_oh_count(aTHX_ self);
381             OUTPUT:
382             RETVAL
383              
384             void
385             oh_Clear(self)
386             SV *self
387             CODE:
388 6           tie_oh_clear(aTHX_ self);