File Coverage

OrderedHash.xs
Criterion Covered Total %
statement 128 136 94.1
branch 92 130 70.7
condition n/a
subroutine n/a
pod n/a
total 220 266 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             * newSVsv preserves the UTF-8 flag; plain newSVpvn(SvPV(...))
120             * would strip it, causing wide-char keys to come back as raw bytes
121             * from FIRSTKEY/NEXTKEY. */
122 10077           SV *key_sv = newSVsv(key);
123             SV *idx_sv;
124             SSize_t newpos;
125 10077           vcopy = newSVsv(value);
126 10077           av_push(keys, key_sv);
127 10077           newpos = av_len(keys);
128 10077 50         if (!av_store(vals, newpos, vcopy))
129 0           SvREFCNT_dec(vcopy);
130 10077           idx_sv = newSViv((IV)newpos);
131 10077 50         if (!hv_store_ent(idx, key_sv, idx_sv, 0))
132 0           SvREFCNT_dec(idx_sv);
133             }
134              
135             int
136             oh_EXISTS(self, key)
137             SV *self
138             SV *key
139             PREINIT:
140             HV *idx; AV *keys; AV *vals;
141             CODE:
142 17           oh_resolve_fast(aTHX_ self, &idx, &keys, &vals);
143 17 50         RETVAL = hv_exists_ent(idx, key, 0) ? 1 : 0;
144             OUTPUT:
145             RETVAL
146              
147             SV *
148             oh_DELETE(self, key)
149             SV *self
150             SV *key
151             PREINIT:
152             STRLEN klen;
153             const char *kpv;
154             SV *got;
155             CODE:
156 20           kpv = SvPV(key, klen);
157 20           got = tie_oh_delete(aTHX_ self, kpv, klen);
158 20 100         RETVAL = got ? newSVsv(got) : newSV(0);
159             OUTPUT:
160             RETVAL
161              
162             void
163             oh_CLEAR(self)
164             SV *self
165             CODE:
166 2           tie_oh_clear(aTHX_ self);
167              
168             SV *
169             oh_FIRSTKEY(self)
170             SV *self
171             PREINIT:
172             AV *keys;
173             SV *cursor;
174             SV **kslot;
175             CODE:
176 82           keys = xs_keys_av_fast(aTHX_ self);
177 82           cursor = xs_cursor_sv_fast(aTHX_ self);
178 82 100         if (av_len(keys) < 0) {
179 25           sv_setiv(cursor, 0);
180 25           XSRETURN_UNDEF;
181             }
182 57           kslot = av_fetch(keys, 0, 0);
183 57 50         if (!kslot || !*kslot) {
    50          
184 0           sv_setiv(cursor, 0);
185 0           XSRETURN_UNDEF;
186             }
187 57           sv_setiv(cursor, 1);
188 57           SvREFCNT_inc_simple_void_NN(*kslot);
189 57           RETVAL = *kslot;
190             OUTPUT:
191             RETVAL
192              
193             SV *
194             oh_NEXTKEY(self, lastkey)
195             SV *self
196             SV *lastkey
197             PREINIT:
198             AV *keys;
199             SV *cursor;
200             SSize_t pos;
201             SV **kslot;
202             CODE:
203             PERL_UNUSED_VAR(lastkey);
204 20143           keys = xs_keys_av_fast(aTHX_ self);
205 20143           cursor = xs_cursor_sv_fast(aTHX_ self);
206 20143           pos = (SSize_t)SvIV(cursor);
207 20143 50         if (pos < 0 || pos > av_len(keys)) XSRETURN_UNDEF;
    100          
208 20092           kslot = av_fetch(keys, pos, 0);
209 20092 50         if (!kslot || !*kslot) XSRETURN_UNDEF;
    50          
210 20092           sv_setiv(cursor, pos + 1);
211 20092           SvREFCNT_inc_simple_void_NN(*kslot);
212 20092           RETVAL = *kslot;
213             OUTPUT:
214             RETVAL
215              
216             int
217             oh_SCALAR(self)
218             SV *self
219             CODE:
220 9 50         RETVAL = tie_oh_count(aTHX_ self) > 0 ? 1 : 0;
221             OUTPUT:
222             RETVAL
223              
224             # ---- OO interface (class methods) ---------------------------------
225              
226             SV *
227             oh_new(class, ...)
228             const char *class
229             PREINIT:
230             SV *self;
231             int i;
232             CODE:
233             PERL_UNUSED_VAR(class);
234 4           self = tie_oh_new(aTHX);
235 4 100         if ((items - 1) % 2 != 0) {
236 1           SvREFCNT_dec(self);
237 1           croak("Tie::OrderedHash::new: odd number of arguments");
238             }
239 8 100         for (i = 1; i + 1 < items; i += 2) {
240             STRLEN klen;
241 5           const char *key = SvPV(ST(i), klen);
242 5           SV *val = newSVsv(ST(i + 1));
243 5           tie_oh_store(aTHX_ self, key, klen, val);
244             }
245 3           RETVAL = self;
246             OUTPUT:
247             RETVAL
248              
249             # Push -- in-order insert/update of (k,v) pairs. Returns post-count.
250             int
251             oh_Push(self, ...)
252             SV *self
253             PREINIT:
254             int i;
255             CODE:
256 5 100         if ((items - 1) % 2 != 0)
257 1           croak("Tie::OrderedHash::Push: odd number of arguments");
258 9 100         for (i = 1; i + 1 < items; i += 2) {
259             STRLEN klen;
260 5           const char *key = SvPV(ST(i), klen);
261 5           SV *val = newSVsv(ST(i + 1));
262 5           tie_oh_store(aTHX_ self, key, klen, val);
263             }
264 4 50         RETVAL = (int)tie_oh_count(aTHX_ self);
265             OUTPUT:
266             RETVAL
267              
268             # Pop -- remove last pair, return (key, value). Empty list on empty.
269             void
270             oh_Pop(self)
271             SV *self
272             PREINIT:
273             SV *kpop, *vpop;
274             PPCODE:
275 10           vpop = oh_pop(aTHX_ self, &kpop);
276 10 100         if (!kpop || !vpop) XSRETURN_EMPTY;
    50          
277 4 50         EXTEND(SP, 2);
278 4           PUSHs(kpop);
279 4           PUSHs(vpop);
280              
281             # Shift -- same but front.
282             void
283             oh_Shift(self)
284             SV *self
285             PREINIT:
286             SV *kshift, *vshift;
287             PPCODE:
288 10           vshift = oh_shift(aTHX_ self, &kshift);
289 10 100         if (!kshift || !vshift) XSRETURN_EMPTY;
    50          
290 4 50         EXTEND(SP, 2);
291 4           PUSHs(kshift);
292 4           PUSHs(vshift);
293              
294             # Unshift -- prepend (k,v) pairs. Per Tie::IxHash, existing keys
295             # are updated in place without changing position.
296             int
297             oh_Unshift(self, ...)
298             SV *self
299             PREINIT:
300             int i;
301             CODE:
302 3 100         if ((items - 1) % 2 != 0)
303 1           croak("Tie::OrderedHash::Unshift: odd number of arguments");
304             /* Walk the trailing args in REVERSE so the supplied list ends
305             * up in source order at the front of the hash. Tie::IxHash's
306             * Unshift documents this behaviour. */
307 5 100         for (i = items - 2; i >= 1; i -= 2) {
308 3           SV *val = newSVsv(ST(i + 1));
309 3           oh_unshift_pair(aTHX_ self, ST(i), val);
310             }
311 2 50         RETVAL = (int)tie_oh_count(aTHX_ self);
312             OUTPUT:
313             RETVAL
314              
315             # Keys: with no args, return the whole keys list in order. With
316             # args, return keys at those indices (negative offsets supported).
317             void
318             oh_Keys(self, ...)
319             SV *self
320             PREINIT:
321             HV *idx; AV *keys; AV *vals;
322             SSize_t i, n;
323             PPCODE:
324 32           oh_resolve(aTHX_ self, &idx, &keys, &vals);
325 32           n = av_len(keys) + 1;
326 32 100         if (items == 1) {
327 13 50         EXTEND(SP, n);
    50          
328 38 100         for (i = 0; i < n; i++) {
329 25           SV **slot = av_fetch(keys, i, 0);
330 25 50         PUSHs(slot && *slot ? sv_mortalcopy(*slot) : &PL_sv_undef);
    50          
331             }
332             } else {
333             int j;
334 19 50         EXTEND(SP, items - 1);
    50          
335 48 100         for (j = 1; j < items; j++) {
336 29           IV want = SvIV(ST(j));
337 29 100         SSize_t pos = want < 0 ? n + want : want;
338 29 100         if (pos < 0 || pos >= n) {
    100          
339 8           PUSHs(&PL_sv_undef);
340             } else {
341 21           SV **slot = av_fetch(keys, pos, 0);
342 21 50         PUSHs(slot && *slot ? sv_mortalcopy(*slot) : &PL_sv_undef);
    50          
343             }
344             }
345             }
346              
347             void
348             oh_Values(self, ...)
349             SV *self
350             PREINIT:
351             HV *idx; AV *keys; AV *vals;
352             SSize_t i, n;
353             PPCODE:
354 24           oh_resolve(aTHX_ self, &idx, &keys, &vals);
355 24           n = av_len(vals) + 1;
356 24 100         if (items == 1) {
357 11 50         EXTEND(SP, n);
    50          
358 33 100         for (i = 0; i < n; i++) {
359 22           SV **slot = av_fetch(vals, i, 0);
360 22 50         PUSHs(slot && *slot ? sv_mortalcopy(*slot) : &PL_sv_undef);
    50          
361             }
362             } else {
363             int j;
364 13 50         EXTEND(SP, items - 1);
    50          
365 31 100         for (j = 1; j < items; j++) {
366 18           IV want = SvIV(ST(j));
367 18 100         SSize_t pos = want < 0 ? n + want : want;
368 18 100         if (pos < 0 || pos >= n) {
    100          
369 3           PUSHs(&PL_sv_undef);
370             } else {
371 15           SV **slot = av_fetch(vals, pos, 0);
372 15 50         PUSHs(slot && *slot ? sv_mortalcopy(*slot) : &PL_sv_undef);
    50          
373             }
374             }
375             }
376              
377             int
378             oh_Length(self)
379             SV *self
380             CODE:
381 19 100         RETVAL = (int)tie_oh_count(aTHX_ self);
382             OUTPUT:
383             RETVAL
384              
385             void
386             oh_Clear(self)
387             SV *self
388             CODE:
389 6           tie_oh_clear(aTHX_ self);