File Coverage

XS.xs
Criterion Covered Total %
statement 753 871 86.4
branch 571 796 71.7
condition n/a
subroutine n/a
pod n/a
total 1324 1667 79.4


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5              
6             #include "XSParseKeyword.h"
7              
8             /* Use stronger inline hint */
9             #ifndef PERL_STATIC_INLINE
10             #define PERL_STATIC_INLINE static inline
11             #endif
12              
13             /* Magic signature for fast type checking (avoids sv_derived_from) */
14             #define COMPILED_PATH_MAGIC 0x44505853 /* "DPXS" */
15              
16             /* Branch prediction hints */
17             #ifndef LIKELY
18             # if defined(__GNUC__) || defined(__clang__)
19             # define LIKELY(x) __builtin_expect(!!(x), 1)
20             # define UNLIKELY(x) __builtin_expect(!!(x), 0)
21             # else
22             # define LIKELY(x) (x)
23             # define UNLIKELY(x) (x)
24             # endif
25             #endif
26              
27             /* Max safe index digits to avoid IV overflow
28             * 64-bit IV: max ~9e18 (19 digits), safe limit 18
29             * 32-bit IV: max ~2e9 (10 digits), safe limit 9
30             */
31             #if IVSIZE >= 8
32             # define MAX_INDEX_DIGITS 18
33             #else
34             # define MAX_INDEX_DIGITS 9
35             #endif
36              
37             /* Path component - pre-parsed */
38             typedef struct {
39             const char *str;
40             STRLEN len;
41             IV idx; /* Pre-parsed array index (valid only if is_numeric) */
42             int is_numeric; /* 1 if component is a valid array index, 0 for hash key */
43             int next_is_array; /* 1 if next component is numeric (create array), 0 for hash */
44             } PathComponent;
45              
46             /* Compiled path object - flexible array member for cache locality */
47             typedef struct {
48             U32 magic; /* COMPILED_PATH_MAGIC for fast type check */
49             SSize_t count;
50             SV *path_sv; /* Owned copy of path string buffer */
51             PathComponent components[1]; /* Flexible array member (C89 style) */
52             } CompiledPath;
53              
54             /* Fast compiled path validation - check ref, NULL, and magic */
55             #define VALIDATE_COMPILED_PATH(sv, cp) do { \
56             if (UNLIKELY(!SvROK(sv))) croak("Not a compiled path"); \
57             cp = INT2PTR(CompiledPath*, SvIV(SvRV(sv))); \
58             if (UNLIKELY(!cp || cp->magic != COMPILED_PATH_MAGIC)) croak("Not a compiled path"); \
59             } while(0)
60              
61             /* Check if string is a valid array index, parse it
62             * Returns 0 for: empty, leading zeros, non-digits, overflow
63             * Accepts negative indices (e.g., "-1" for last element)
64             * Note: "-0" parses as 0 (single zero after minus is allowed)
65             * Max safe index: we limit to MAX_INDEX_DIGITS digits to avoid IV overflow
66             */
67 59519           PERL_STATIC_INLINE int is_array_index(const char *s, STRLEN len, IV *idx) {
68 59519           IV val = 0;
69 59519           const char *end = s + len;
70 59519           int negative = 0;
71              
72 59519 50         if (UNLIKELY(len == 0 || len > (MAX_INDEX_DIGITS + 1))) return 0; /* Empty or too long */
    100          
73              
74             /* Handle negative sign */
75 59514 100         if (*s == '-') {
76 17           negative = 1;
77 17           s++;
78 17           len--;
79 17 50         if (UNLIKELY(len == 0)) return 0; /* Just "-" */
80             }
81              
82 59514 100         if (UNLIKELY(len > MAX_INDEX_DIGITS)) return 0; /* Too many digits */
83 59513 100         if (len > 1 && *s == '0') return 0; /* No leading zeros */
    100          
84              
85 101707 100         while (s < end) {
86 59524 100         if (UNLIKELY(*s < '0' || *s > '9')) return 0;
    100          
87 42202           val = val * 10 + (*s - '0');
88 42202           s++;
89             }
90              
91 42183 100         *idx = negative ? -val : val;
92 42183           return 1;
93             }
94              
95             /* Fast SV to index - check IOK first, accept negative for Perl-style access */
96 4087           PERL_STATIC_INLINE int sv_to_index(pTHX_ SV *sv, IV *idx) {
97 4087 100         if (SvIOK(sv)) {
98 68           *idx = SvIVX(sv);
99 68           return 1;
100             }
101             STRLEN len;
102 4019           const char *s = SvPV(sv, len);
103 4019           return is_array_index(s, len, idx);
104             }
105              
106             /* Navigate using raw char* path components */
107 109230           PERL_STATIC_INLINE SV* navigate_to_parent(pTHX_ SV *data, const char *path, STRLEN path_len,
108             const char **final_key_ptr, STRLEN *final_key_len, int create) {
109 109230           const char *p = path;
110 109230           const char *end = path + path_len;
111 109230           SV *current = data;
112              
113 109230 50         if (UNLIKELY(path_len == 0)) {
114 0           *final_key_ptr = NULL;
115 0           *final_key_len = 0;
116 0           return data;
117             }
118              
119             /* Skip leading slash if present (optional for consistency with keyword API) */
120 109230 100         if (*p == '/') p++;
121              
122 246180 100         while (p < end) {
123 246176           const char *tok_start = p;
124 879890 100         while (p < end && *p != '/') p++;
    100          
125 246176           STRLEN tok_len = p - tok_start;
126              
127             /* Skip empty components (e.g., double slashes, trailing slashes) */
128 246176 100         if (UNLIKELY(tok_len == 0)) {
129 13           p++; /* Always advance to avoid infinite loop */
130 13           continue;
131             }
132              
133             /* Skip trailing slashes to check if this is the last real component */
134 246163           const char *check = p;
135 391126 100         while (check < end && *check == '/') check++;
    100          
136              
137             /* Last non-empty token - return parent */
138 246163 100         if (check >= end) {
139 101219           *final_key_ptr = tok_start;
140 101219           *final_key_len = tok_len;
141 101219           return current;
142             }
143              
144             /* Navigate deeper */
145 144944 50         if (UNLIKELY(!SvROK(current))) {
146 0           *final_key_ptr = NULL;
147 0           return NULL;
148             }
149              
150 144944           SV *inner = SvRV(current);
151 144944           svtype t = SvTYPE(inner);
152              
153 144944 100         if (LIKELY(t == SVt_PVHV)) {
154 142922           HV *hv = (HV*)inner;
155 142922           SV **val = hv_fetch(hv, tok_start, tok_len, 0);
156 142922 100         if (UNLIKELY(!val || !*val || !SvROK(*val))) {
    50          
    100          
    100          
157 20834 100         if (create) {
158             /* check already points at next real component start */
159 12828           const char *next_end = check;
160 25668 100         while (next_end < end && *next_end != '/') next_end++;
    100          
161             IV dummy;
162 12828           SV *new_ref = is_array_index(check, next_end - check, &dummy)
163 9           ? newRV_noinc((SV*)newAV())
164 12828 100         : newRV_noinc((SV*)newHV());
165 12828 50         if (UNLIKELY(!hv_store(hv, tok_start, tok_len, new_ref, 0))) {
166 0           SvREFCNT_dec(new_ref);
167 0           *final_key_ptr = NULL;
168 0           return NULL;
169             }
170 12828           current = new_ref;
171             } else {
172 8006           *final_key_ptr = NULL;
173 8006           return NULL;
174             }
175             } else {
176 122088           current = *val;
177             }
178 2022 50         } else if (t == SVt_PVAV) {
179             IV idx;
180 2022 50         if (UNLIKELY(!is_array_index(tok_start, tok_len, &idx))) {
181 0           *final_key_ptr = NULL;
182 1           return NULL;
183             }
184 2022           SV **val = av_fetch((AV*)inner, idx, 0);
185 2022 100         if (UNLIKELY(!val || !*val || !SvROK(*val))) {
    50          
    100          
    50          
186 8 100         if (create) {
187             /* check already points at next real component start */
188 7           const char *next_end = check;
189 19 100         while (next_end < end && *next_end != '/') next_end++;
    100          
190             IV dummy;
191 7           SV *new_ref = is_array_index(check, next_end - check, &dummy)
192 4           ? newRV_noinc((SV*)newAV())
193 7 100         : newRV_noinc((SV*)newHV());
194 7 50         if (UNLIKELY(!av_store((AV*)inner, idx, new_ref))) {
195 0           SvREFCNT_dec(new_ref);
196 0           *final_key_ptr = NULL;
197 0           return NULL;
198             }
199 7           current = new_ref;
200             } else {
201 1           *final_key_ptr = NULL;
202 1           return NULL;
203             }
204             } else {
205 2014           current = *val;
206             }
207             } else {
208 0           *final_key_ptr = NULL;
209 0           return NULL;
210             }
211              
212 136937           p++;
213             }
214              
215 4           *final_key_ptr = NULL;
216 4           return current;
217             }
218              
219             /* Compile a path string into reusable components */
220 48           static CompiledPath* compile_path(pTHX_ SV *path_sv) {
221             STRLEN path_len;
222 48           const char *path = SvPV(path_sv, path_len);
223             CompiledPath *cp;
224 48           SSize_t count = 0;
225             const char *p;
226 48           const char *path_end = path + path_len;
227              
228             /* Skip leading slash */
229 48           const char *start = path;
230 48 100         if (path_len > 0 && *start == '/') start++;
    100          
231              
232             /* Count non-empty components (skip double slashes) */
233 48           p = start;
234 151 100         while (p < path_end) {
235 103           const char *tok_start = p;
236 271 100         while (p < path_end && *p != '/') p++;
    100          
237 103 50         if (p > tok_start) count++;
238 103 100         if (p < path_end) p++;
239             }
240              
241             /* Empty path, root path ("/"), or all-slashes ("///") */
242 48 100         if (count == 0) {
243 5           Newxz(cp, 1, CompiledPath);
244 5           cp->magic = COMPILED_PATH_MAGIC;
245 5           cp->count = 0;
246 5           cp->path_sv = newSVpvn(path, path_len);
247 5           return cp;
248             }
249              
250             /* Allocate struct with inline component array in single allocation
251             * Size = base struct + (count-1) extra PathComponents
252             * (struct already includes space for 1 component)
253             */
254 43           Size_t alloc_size = sizeof(CompiledPath);
255 43 100         if (count > 1) {
256 41           alloc_size += (count - 1) * sizeof(PathComponent);
257             }
258 43           Newxc(cp, alloc_size, char, CompiledPath);
259              
260 43           cp->magic = COMPILED_PATH_MAGIC;
261 43           cp->count = count;
262             /* Create independent copy of path string so PathComponent pointers
263             * remain valid even if the original SV's buffer is modified/freed */
264 43           cp->path_sv = newSVpvn(path, path_len);
265              
266             /* Re-derive pointers into our own copy's buffer */
267 43           const char *copy_buf = SvPVX(cp->path_sv);
268 43           const char *copy_start = copy_buf + (start - path);
269 43           const char *copy_end = copy_buf + path_len;
270              
271             /* Parse components directly into inline array, skipping empty ones */
272 43           p = copy_start;
273 43           SSize_t i = 0;
274 146 100         while (p < copy_end && i < count) {
    50          
275 103           const char *tok_start = p;
276 271 100         while (p < copy_end && *p != '/') p++;
    100          
277 103           STRLEN tok_len = p - tok_start;
278 103 50         if (tok_len > 0) {
279 103           cp->components[i].str = tok_start;
280 103           cp->components[i].len = tok_len;
281 103           cp->components[i].is_numeric = is_array_index(tok_start, tok_len, &cp->components[i].idx);
282 103           cp->components[i].next_is_array = 0; /* Will set below */
283 103           i++;
284             }
285 103 100         if (p < copy_end) p++; /* Skip slash */
286             }
287              
288             /* Pre-compute next_is_array flag for faster creation */
289 103 100         for (SSize_t j = 0; j < count - 1; j++) {
290 60           cp->components[j].next_is_array = cp->components[j + 1].is_numeric;
291             }
292 43           cp->components[count - 1].next_is_array = 0; /* Last element */
293              
294 43           return cp;
295             }
296              
297 48           static void free_compiled_path(pTHX_ CompiledPath *cp) {
298 48 50         if (cp) {
299 48           SvREFCNT_dec(cp->path_sv);
300 48           Safefree(cp);
301             }
302 48           }
303              
304             /* ========== KEYWORD SUPPORT ========== */
305              
306             /* Custom ops for dynamic path access - runs directly in runloop */
307             static XOP xop_pathget;
308             static XOP xop_pathset;
309             static XOP xop_pathdelete;
310             static XOP xop_pathexists;
311              
312 76           static OP* pp_pathget_dynamic(pTHX)
313             {
314 76           dSP;
315 76           SV *path_sv = POPs;
316 76           SV *data_sv = POPs;
317              
318             STRLEN path_len;
319 76           const char *path = SvPV(path_sv, path_len);
320 76           const char *p = path;
321 76           const char *end = path + path_len;
322              
323 76           SV *current = data_sv;
324              
325             /* Skip leading slashes */
326 150 100         while (p < end && *p == '/') p++;
    100          
327              
328 264 100         while (p < end && SvOK(current)) {
    100          
329             /* Find component end */
330 189           const char *start = p;
331 1790 100         while (p < end && *p != '/') p++;
    100          
332 189           STRLEN comp_len = p - start;
333              
334 189 100         if (UNLIKELY(comp_len == 0)) {
335 5 50         if (p < end) p++;
336 5           continue;
337             }
338              
339 184 100         if (UNLIKELY(!SvROK(current))) {
340 1           current = &PL_sv_undef;
341 1           break;
342             }
343              
344 183           SV *inner = SvRV(current);
345             IV idx;
346              
347 183 100         if (is_array_index(start, comp_len, &idx)) {
348 42 50         if (UNLIKELY(SvTYPE(inner) != SVt_PVAV)) {
349 0           current = &PL_sv_undef;
350 0           break;
351             }
352 42           SV **elem = av_fetch((AV*)inner, idx, 0);
353 42 100         current = elem ? *elem : &PL_sv_undef;
354             } else {
355 141 50         if (UNLIKELY(SvTYPE(inner) != SVt_PVHV)) {
356 0           current = &PL_sv_undef;
357 0           break;
358             }
359 141           SV **svp = hv_fetch((HV*)inner, start, comp_len, 0);
360 141 100         current = svp ? *svp : &PL_sv_undef;
361             }
362              
363 183 100         if (p < end && *p == '/') p++;
    50          
364             }
365              
366 76           PUSHs(current);
367 76           RETURN;
368             }
369              
370             /* Helper to check if next path component is numeric (including negative) */
371 20           PERL_STATIC_INLINE int kw_next_component_is_numeric(const char *p, const char *end)
372             {
373 20 50         if (p >= end || *p != '/') return 0;
    50          
374             /* Skip consecutive slashes to find next real component */
375 44 50         while (p < end && *p == '/') p++;
    100          
376 20 50         if (p >= end) return 0;
377              
378             /* Handle optional negative sign */
379 20 50         if (*p == '-') {
380 0           p++;
381 0 0         if (p >= end || *p == '/') return 0; /* Just "-" */
    0          
382             }
383              
384 20           const char *digits_start = p;
385 26 100         while (p < end && *p != '/') {
    100          
386 20 100         if (*p < '0' || *p > '9') return 0;
    100          
387 6           p++;
388             }
389             /* Use same rules as is_array_index: no leading zeros, max digits */
390 6           STRLEN len = p - digits_start;
391 6 50         if (UNLIKELY(len == 0 || len > MAX_INDEX_DIGITS)) return 0;
    50          
392 6 50         if (len > 1 && *digits_start == '0') return 0;
    0          
393 6           return 1;
394             }
395              
396             /* Custom op for dynamic path set with autovivification */
397 27           static OP* pp_pathset_dynamic(pTHX)
398             {
399 27           dSP; dMARK; dORIGMARK;
400 27           SV *data_sv = *++MARK;
401 27           SV *path_sv = *++MARK;
402 27           SV *value_sv = *++MARK;
403 27           SP = ORIGMARK; /* Reset stack to before our args */
404              
405             STRLEN path_len;
406 27           const char *path = SvPV(path_sv, path_len);
407 27           const char *p = path;
408 27           const char *end = path + path_len;
409              
410             /* Skip leading slashes */
411 56 100         while (p < end && *p == '/') p++;
    100          
412              
413 27 100         if (UNLIKELY(p >= end)) {
414             /* Empty path (or all slashes) - can't set root */
415 4           croak("Cannot set root");
416             }
417              
418 23           SV *current = data_sv;
419              
420 73 100         while (p < end) {
421             /* Find component end */
422 52           const char *start = p;
423 1174 100         while (p < end && *p != '/') p++;
    100          
424 52           STRLEN comp_len = p - start;
425              
426 52 100         if (UNLIKELY(comp_len == 0)) {
427 4 50         if (p < end) p++;
428 4           continue;
429             }
430              
431             /* Skip all trailing slashes/empty components for "is last" check */
432 48           const char *next_p = p;
433 77 100         while (next_p < end && *next_p == '/') next_p++;
    100          
434 48           int is_last = (next_p >= end);
435              
436 48 50         if (UNLIKELY(!SvROK(current))) {
437 0           croak("Cannot navigate to path");
438             }
439              
440 48           SV *inner = SvRV(current);
441             IV idx;
442              
443 48 100         if (is_array_index(start, comp_len, &idx)) {
444             /* Array access */
445 10 50         if (UNLIKELY(SvTYPE(inner) != SVt_PVAV)) {
446 0           croak("Cannot navigate to path");
447             }
448 10           AV *av = (AV*)inner;
449              
450 10 100         if (is_last) {
451             /* Final component - store value */
452 8 50         SV *copy = SvROK(value_sv) ? SvREFCNT_inc(value_sv) : newSVsv(value_sv);
453 8 100         if (UNLIKELY(!av_store(av, idx, copy))) {
454 1           SvREFCNT_dec(copy);
455 1           croak("Failed to store value");
456             }
457             } else {
458             /* Intermediate - autovivify if needed */
459 2           SV **elem = av_fetch(av, idx, 1); /* 1 = lvalue/create */
460 2 50         if (UNLIKELY(!elem)) {
461 0           croak("Cannot navigate to path");
462             }
463 2 50         if (!SvOK(*elem) || !SvROK(*elem)) {
    0          
464             /* Autovivify based on next component */
465             SV *new_ref;
466 2 50         if (kw_next_component_is_numeric(p, end)) {
467 0           new_ref = newRV_noinc((SV*)newAV());
468             } else {
469 2           new_ref = newRV_noinc((SV*)newHV());
470             }
471 2           sv_setsv(*elem, new_ref);
472 2           SvREFCNT_dec(new_ref);
473             }
474 2           current = *elem;
475             }
476             } else {
477             /* Hash access */
478 38 100         if (UNLIKELY(SvTYPE(inner) != SVt_PVHV)) {
479 1           croak("Cannot navigate to path");
480             }
481 37           HV *hv = (HV*)inner;
482              
483 37 100         if (is_last) {
484             /* Final component - store value */
485 14 100         SV *copy = SvROK(value_sv) ? SvREFCNT_inc(value_sv) : newSVsv(value_sv);
486 14 50         if (UNLIKELY(!hv_store(hv, start, comp_len, copy, 0))) {
487 0           SvREFCNT_dec(copy);
488 0           croak("Failed to store value");
489             }
490             } else {
491             /* Intermediate - autovivify if needed */
492 23           SV **elem = hv_fetch(hv, start, comp_len, 1); /* 1 = lvalue/create */
493 23 50         if (UNLIKELY(!elem)) {
494 0           croak("Cannot navigate to path");
495             }
496 23 100         if (!SvOK(*elem) || !SvROK(*elem)) {
    100          
497             /* Autovivify based on next component */
498             SV *new_ref;
499 18 100         if (kw_next_component_is_numeric(p, end)) {
500 6           new_ref = newRV_noinc((SV*)newAV());
501             } else {
502 12           new_ref = newRV_noinc((SV*)newHV());
503             }
504 18           sv_setsv(*elem, new_ref);
505 18           SvREFCNT_dec(new_ref);
506             }
507 23           current = *elem;
508             }
509             }
510              
511 46 100         if (p < end && *p == '/') p++;
    50          
512             }
513              
514 21 100         if (GIMME_V != G_VOID)
515 2           PUSHs(value_sv);
516 21           RETURN;
517             }
518              
519             /* Custom op for dynamic path delete */
520 32           static OP* pp_pathdelete_dynamic(pTHX)
521             {
522 32           dSP;
523 32           SV *path_sv = POPs;
524 32           SV *data_sv = POPs;
525              
526             STRLEN path_len;
527 32           const char *path = SvPV(path_sv, path_len);
528 32           const char *p = path;
529 32           const char *end = path + path_len;
530              
531             /* Skip leading slashes */
532 66 100         while (p < end && *p == '/') p++;
    100          
533              
534 32 100         if (p >= end) {
535             /* Empty path (or all slashes) - can't delete root */
536 3           croak("Cannot delete root");
537             }
538              
539 29           SV *current = data_sv;
540 29           SV *parent = NULL;
541 29           const char *last_key_start = NULL;
542 29           STRLEN last_key_len = 0;
543 29           int last_is_numeric = 0;
544 29           IV last_idx = 0;
545              
546 65 50         while (p < end) {
547             /* Find component end */
548 65           const char *start = p;
549 221 100         while (p < end && *p != '/') p++;
    100          
550 65           STRLEN comp_len = p - start;
551              
552 65 50         if (UNLIKELY(comp_len == 0)) {
553 0 0         if (p < end) p++;
554 0           continue;
555             }
556              
557             /* Check if numeric using shared helper */
558             IV idx;
559 65           int is_numeric = is_array_index(start, comp_len, &idx);
560              
561             /* Skip all trailing slashes/empty components for "is last" check */
562 65           const char *next_p = p;
563 105 100         while (next_p < end && *next_p == '/') next_p++;
    100          
564 65           int is_last = (next_p >= end);
565              
566 65 100         if (is_last) {
567             /* Remember this for deletion */
568 27           parent = current;
569 27           last_key_start = start;
570 27           last_key_len = comp_len;
571 27           last_is_numeric = is_numeric;
572 27           last_idx = idx;
573 27           break;
574             }
575              
576 38 100         if (UNLIKELY(!SvROK(current))) {
577 2           PUSHs(&PL_sv_undef);
578 2           RETURN;
579             }
580              
581 36           SV *inner = SvRV(current);
582              
583 36 100         if (is_numeric) {
584 2 50         if (UNLIKELY(SvTYPE(inner) != SVt_PVAV)) {
585 0           PUSHs(&PL_sv_undef);
586 0           RETURN;
587             }
588 2           SV **elem = av_fetch((AV*)inner, idx, 0);
589 2 50         current = elem ? *elem : &PL_sv_undef;
590             } else {
591 34 50         if (UNLIKELY(SvTYPE(inner) != SVt_PVHV)) {
592 0           PUSHs(&PL_sv_undef);
593 0           RETURN;
594             }
595 34           SV **svp = hv_fetch((HV*)inner, start, comp_len, 0);
596 34 50         current = svp ? *svp : &PL_sv_undef;
597             }
598              
599 36 50         if (p < end && *p == '/') p++;
    50          
600             }
601              
602             /* Perform the delete */
603 27           SV *deleted = NULL;
604 27 50         if (parent && SvROK(parent)) {
    50          
605 27           SV *inner = SvRV(parent);
606 27 100         if (last_is_numeric) {
607 6 50         if (SvTYPE(inner) == SVt_PVAV)
608 6           deleted = av_delete((AV*)inner, last_idx, 0);
609             } else {
610 21 50         if (SvTYPE(inner) == SVt_PVHV)
611 21           deleted = hv_delete((HV*)inner, last_key_start, last_key_len, 0);
612             }
613             }
614              
615 27 100         if (deleted) {
616 25           SvREFCNT_inc_simple_void_NN(deleted);
617 25           PUSHs(sv_2mortal(deleted));
618             } else {
619 2           PUSHs(&PL_sv_undef);
620             }
621 27           RETURN;
622             }
623              
624             /* Custom op for dynamic path exists check */
625 39           static OP* pp_pathexists_dynamic(pTHX)
626             {
627 39           dSP;
628 39           SV *path_sv = POPs;
629 39           SV *data_sv = POPs;
630              
631             STRLEN path_len;
632 39           const char *path = SvPV(path_sv, path_len);
633 39           const char *p = path;
634 39           const char *end = path + path_len;
635              
636             /* Skip leading slashes */
637 77 100         while (p < end && *p == '/') p++;
    100          
638              
639 39 100         if (p >= end) {
640             /* Empty path - root always exists (consistent with path_exists) */
641 4           PUSHs(&PL_sv_yes);
642 4           RETURN;
643             }
644              
645 35           SV *current = data_sv;
646              
647 81 50         while (p < end) {
648             /* Find component end */
649 81           const char *start = p;
650 270 100         while (p < end && *p != '/') p++;
    100          
651 81           STRLEN comp_len = p - start;
652              
653 81 50         if (UNLIKELY(comp_len == 0)) {
654 0 0         if (p < end) p++;
655 0           continue;
656             }
657              
658             /* Check if numeric using shared helper */
659             IV idx;
660 81           int is_numeric = is_array_index(start, comp_len, &idx);
661              
662             /* Skip all trailing slashes/empty components for "is last" check */
663 81           const char *next_p = p;
664 131 100         while (next_p < end && *next_p == '/') next_p++;
    100          
665 81           int is_last = (next_p >= end);
666              
667 81 50         if (UNLIKELY(!SvROK(current))) {
668 0           PUSHs(&PL_sv_no);
669 35           RETURN;
670             }
671              
672 81           SV *inner = SvRV(current);
673              
674 81 100         if (is_numeric) {
675 12 50         if (UNLIKELY(SvTYPE(inner) != SVt_PVAV)) {
676 0           PUSHs(&PL_sv_no);
677 0           RETURN;
678             }
679 12           AV *av = (AV*)inner;
680              
681 12 100         if (is_last) {
682             /* Final component - check if exists */
683 8           bool exists = av_exists(av, idx);
684 8 100         PUSHs(exists ? &PL_sv_yes : &PL_sv_no);
685 8           RETURN;
686             } else {
687 4           SV **elem = av_fetch(av, idx, 0);
688 4 100         if (!elem || !SvOK(*elem)) {
    50          
689 1           PUSHs(&PL_sv_no);
690 1           RETURN;
691             }
692 3           current = *elem;
693             }
694             } else {
695 69 50         if (UNLIKELY(SvTYPE(inner) != SVt_PVHV)) {
696 0           PUSHs(&PL_sv_no);
697 0           RETURN;
698             }
699 69           HV *hv = (HV*)inner;
700              
701 69 100         if (is_last) {
702             /* Final component - check if exists */
703 25           bool exists = hv_exists(hv, start, comp_len);
704 25 100         PUSHs(exists ? &PL_sv_yes : &PL_sv_no);
705 25           RETURN;
706             } else {
707 44           SV **svp = hv_fetch(hv, start, comp_len, 0);
708 44 100         if (!svp || !SvOK(*svp)) {
    50          
709 1           PUSHs(&PL_sv_no);
710 1           RETURN;
711             }
712 43           current = *svp;
713             }
714             }
715              
716 46 50         if (p < end && *p == '/') p++;
    50          
717             }
718              
719             /* All components consumed (or path was all slashes) - root/value exists */
720 0           PUSHs(&PL_sv_yes);
721 0           RETURN;
722             }
723              
724             /* Build a custom op for dynamic path access */
725 70           static OP* kw_build_dynamic_pathget(pTHX_ OP *data_op, OP *path_op)
726             {
727             /* Create BINOP with data and path as children */
728 70           OP *binop = newBINOP(OP_NULL, 0, data_op, path_op);
729 70           binop->op_type = OP_CUSTOM;
730 70           binop->op_ppaddr = pp_pathget_dynamic;
731              
732 70           return binop;
733             }
734              
735             /* Build a chain of hash/array element accesses for assignment (lvalue) */
736 43           static OP* kw_build_deref_chain_lvalue(pTHX_ OP *data_op, const char *path, STRLEN path_len)
737             {
738 43           OP *current = data_op;
739 43           const char *p = path;
740 43           const char *end = path + path_len;
741              
742             /* Skip leading slash */
743 43 50         if (p < end && *p == '/') p++;
    100          
744              
745 170 100         while (p < end) {
746             /* Find component end */
747 127           const char *start = p;
748 571 100         while (p < end && *p != '/') p++;
    100          
749 127           STRLEN comp_len = p - start;
750              
751 127 100         if (comp_len == 0) {
752 3 50         if (p < end) p++;
753 3           continue;
754             }
755              
756             /* Check if this component is numeric */
757             IV idx;
758 124           int is_numeric = is_array_index(start, comp_len, &idx);
759              
760 124 100         if (is_numeric) {
761             /* Array element: $current->[$idx] */
762 27           current = newBINOP(OP_AELEM, OPf_MOD,
763             newUNOP(OP_RV2AV, OPf_REF | OPf_MOD, current),
764             newSVOP(OP_CONST, 0, newSViv(idx)));
765             } else {
766             /* Hash element: $current->{key} */
767 97           current = newBINOP(OP_HELEM, OPf_MOD,
768             newUNOP(OP_RV2HV, OPf_REF | OPf_MOD, current),
769             newSVOP(OP_CONST, 0, newSVpvn(start, comp_len)));
770             }
771              
772 124 100         if (p < end && *p == '/') p++;
    50          
773             }
774              
775 43           return current;
776             }
777              
778             /* Build a custom op for dynamic path set */
779 24           static OP* kw_build_dynamic_pathset(pTHX_ OP *data_op, OP *path_op, OP *value_op)
780             {
781             /* Build list: pushmark, data, path, value */
782 24           OP *pushmark = newOP(OP_PUSHMARK, 0);
783 24           OP *list = op_append_elem(OP_LIST, pushmark, data_op);
784 24           list = op_append_elem(OP_LIST, list, path_op);
785 24           list = op_append_elem(OP_LIST, list, value_op);
786              
787             /* Convert to custom op */
788 24           OP *custom = op_convert_list(OP_NULL, 0, list);
789 24           custom->op_type = OP_CUSTOM;
790 24           custom->op_ppaddr = pp_pathset_dynamic;
791              
792 24           return custom;
793             }
794              
795             /* Build a custom op for dynamic path delete */
796 29           static OP* kw_build_dynamic_pathdelete(pTHX_ OP *data_op, OP *path_op)
797             {
798             /* Create BINOP with data and path as children */
799 29           OP *binop = newBINOP(OP_NULL, 0, data_op, path_op);
800 29           binop->op_type = OP_CUSTOM;
801 29           binop->op_ppaddr = pp_pathdelete_dynamic;
802              
803 29           return binop;
804             }
805              
806             /* Build a custom op for dynamic path exists */
807 36           static OP* kw_build_dynamic_pathexists(pTHX_ OP *data_op, OP *path_op)
808             {
809             /* Create BINOP with data and path as children */
810 36           OP *binop = newBINOP(OP_NULL, 0, data_op, path_op);
811 36           binop->op_type = OP_CUSTOM;
812 36           binop->op_ppaddr = pp_pathexists_dynamic;
813              
814 36           return binop;
815             }
816              
817             /* The build callback for 'pathget' keyword */
818 70           static int build_kw_pathget(pTHX_ OP **out, XSParseKeywordPiece *args[],
819             size_t nargs, void *hookdata)
820             {
821             PERL_UNUSED_ARG(nargs);
822             PERL_UNUSED_ARG(hookdata);
823              
824 70           OP *data_op = args[0]->op;
825 70           OP *path_op = args[1]->op;
826              
827             /* Always use custom op to avoid autovivification of intermediate levels */
828 70           *out = kw_build_dynamic_pathget(aTHX_ data_op, path_op);
829 70           return KEYWORD_PLUGIN_EXPR;
830             }
831              
832             /* Keyword hooks structure for pathget */
833             static const struct XSParseKeywordHooks hooks_pathget = {
834             .permit_hintkey = "Data::Path::XS/pathget",
835             .pieces = (const struct XSParseKeywordPieceType []) {
836             XPK_TERMEXPR, /* data structure */
837             XPK_COMMA, /* , */
838             XPK_TERMEXPR, /* path */
839             {0}
840             },
841             .build = &build_kw_pathget,
842             };
843              
844             /* The build callback for 'pathset' keyword */
845 69           static int build_kw_pathset(pTHX_ OP **out, XSParseKeywordPiece *args[],
846             size_t nargs, void *hookdata)
847             {
848             PERL_UNUSED_ARG(nargs);
849             PERL_UNUSED_ARG(hookdata);
850              
851 69           OP *data_op = args[0]->op;
852 69           OP *path_op = args[1]->op;
853 69           OP *value_op = args[2]->op;
854              
855             /* Check if path is a compile-time constant string */
856 69 100         if (path_op->op_type == OP_CONST &&
857 45 50         (path_op->op_private & OPpCONST_BARE) == 0)
858             {
859 45           SV *path_sv = cSVOPx(path_op)->op_sv;
860 45 50         if (SvPOK(path_sv)) {
861             STRLEN path_len;
862 45           const char *path = SvPV(path_sv, path_len);
863              
864             /* Validate: non-empty path with at least one component */
865 45           const char *p = path;
866 91 100         while (p < path + path_len && *p == '/') p++;
    100          
867 45 100         if (p >= path + path_len) {
868 2           croak("Cannot set root");
869             }
870              
871             /* Build: $data->{a}{b}{c} = $value */
872 43           OP *lvalue = kw_build_deref_chain_lvalue(aTHX_ data_op, path, path_len);
873              
874             /* Mark as lvalue for proper autovivification */
875 43           lvalue = op_lvalue(lvalue, OP_SASSIGN);
876              
877 43           *out = newBINOP(OP_SASSIGN, 0, value_op, lvalue);
878              
879             /* Free the constant path op */
880 43           op_free(path_op);
881              
882 43           return KEYWORD_PLUGIN_EXPR;
883             }
884             }
885              
886             /* Dynamic path - use custom op */
887 24           *out = kw_build_dynamic_pathset(aTHX_ data_op, path_op, value_op);
888 24           return KEYWORD_PLUGIN_EXPR;
889             }
890              
891             /* Keyword hooks structure for pathset */
892             static const struct XSParseKeywordHooks hooks_pathset = {
893             .permit_hintkey = "Data::Path::XS/pathset",
894             .pieces = (const struct XSParseKeywordPieceType []) {
895             XPK_TERMEXPR, /* data structure */
896             XPK_COMMA, /* , */
897             XPK_TERMEXPR, /* path */
898             XPK_COMMA, /* , */
899             XPK_TERMEXPR, /* value */
900             {0}
901             },
902             .build = &build_kw_pathset,
903             };
904              
905             /* The build callback for 'pathdelete' keyword */
906 29           static int build_kw_pathdelete(pTHX_ OP **out, XSParseKeywordPiece *args[],
907             size_t nargs, void *hookdata)
908             {
909             PERL_UNUSED_ARG(nargs);
910             PERL_UNUSED_ARG(hookdata);
911              
912 29           OP *data_op = args[0]->op;
913 29           OP *path_op = args[1]->op;
914              
915             /* Always use custom op to avoid autovivification of intermediate levels */
916 29           *out = kw_build_dynamic_pathdelete(aTHX_ data_op, path_op);
917 29           return KEYWORD_PLUGIN_EXPR;
918             }
919              
920             /* Keyword hooks structure for pathdelete */
921             static const struct XSParseKeywordHooks hooks_pathdelete = {
922             .permit_hintkey = "Data::Path::XS/pathdelete",
923             .pieces = (const struct XSParseKeywordPieceType []) {
924             XPK_TERMEXPR, /* data structure */
925             XPK_COMMA, /* , */
926             XPK_TERMEXPR, /* path */
927             {0}
928             },
929             .build = &build_kw_pathdelete,
930             };
931              
932             /* The build callback for 'pathexists' keyword */
933 36           static int build_kw_pathexists(pTHX_ OP **out, XSParseKeywordPiece *args[],
934             size_t nargs, void *hookdata)
935             {
936             PERL_UNUSED_ARG(nargs);
937             PERL_UNUSED_ARG(hookdata);
938              
939 36           OP *data_op = args[0]->op;
940 36           OP *path_op = args[1]->op;
941              
942             /* Always use custom op to avoid autovivification of intermediate levels */
943 36           *out = kw_build_dynamic_pathexists(aTHX_ data_op, path_op);
944 36           return KEYWORD_PLUGIN_EXPR;
945             }
946              
947             /* Keyword hooks structure for pathexists */
948             static const struct XSParseKeywordHooks hooks_pathexists = {
949             .permit_hintkey = "Data::Path::XS/pathexists",
950             .pieces = (const struct XSParseKeywordPieceType []) {
951             XPK_TERMEXPR, /* data structure */
952             XPK_COMMA, /* , */
953             XPK_TERMEXPR, /* path */
954             {0}
955             },
956             .build = &build_kw_pathexists,
957             };
958              
959             /* ========== END KEYWORD SUPPORT ========== */
960              
961             MODULE = Data::Path::XS PACKAGE = Data::Path::XS
962              
963             PROTOTYPES: DISABLE
964              
965             SV*
966             path_get(data, path)
967             SV *data
968             SV *path
969             CODE:
970             STRLEN path_len;
971 44254           const char *path_str = SvPV(path, path_len);
972             const char *final_key;
973             STRLEN final_key_len;
974              
975 44254 100         if (path_len == 0) {
976 4           RETVAL = SvREFCNT_inc(data);
977             } else {
978 44250           SV *parent = navigate_to_parent(aTHX_ data, path_str, path_len, &final_key, &final_key_len, 0);
979              
980             /* final_key == NULL means path refers to root (e.g., "/" or "///") */
981 44250 100         if (!final_key) {
982 4005 100         RETVAL = parent ? SvREFCNT_inc(parent) : &PL_sv_undef;
983 40245 50         } else if (!parent || !SvROK(parent)) {
    50          
984 0           RETVAL = &PL_sv_undef;
985             } else {
986 40245           SV *inner = SvRV(parent);
987 40245           svtype t = SvTYPE(inner);
988              
989 40245 100         if (t == SVt_PVHV) {
990 20229           SV **val = hv_fetch((HV*)inner, final_key, final_key_len, 0);
991 20229 100         RETVAL = (val && *val) ? SvREFCNT_inc(*val) : &PL_sv_undef;
    50          
992 20016 50         } else if (t == SVt_PVAV) {
993             IV idx;
994 20016 100         if (is_array_index(final_key, final_key_len, &idx)) {
995 20012           SV **val = av_fetch((AV*)inner, idx, 0);
996 20012 100         RETVAL = (val && *val) ? SvREFCNT_inc(*val) : &PL_sv_undef;
    50          
997             } else {
998 4           RETVAL = &PL_sv_undef;
999             }
1000             } else {
1001 0           RETVAL = &PL_sv_undef;
1002             }
1003             }
1004             }
1005             OUTPUT:
1006             RETVAL
1007              
1008             SV*
1009             path_set(data, path, value)
1010             SV *data
1011             SV *path
1012             SV *value
1013             CODE:
1014             STRLEN path_len;
1015 40537           const char *path_str = SvPV(path, path_len);
1016             const char *final_key;
1017             STRLEN final_key_len;
1018              
1019 40537 100         if (path_len == 0) {
1020 2001           croak("Cannot set root");
1021             }
1022              
1023 38536           SV *parent = navigate_to_parent(aTHX_ data, path_str, path_len, &final_key, &final_key_len, 1);
1024              
1025             /* final_key == NULL with parent means path refers to root - can't set
1026             * final_key == NULL without parent means navigation failed */
1027 38536 100         if (!final_key) {
1028 1 50         croak(parent ? "Cannot set root" : "Cannot navigate to path");
1029             }
1030 38535 50         if (!parent || !SvROK(parent)) {
    50          
1031 0           croak("Cannot navigate to path");
1032             }
1033              
1034 38535           SV *inner = SvRV(parent);
1035 38535           svtype t = SvTYPE(inner);
1036 38535 100         SV *copy = SvROK(value) ? SvREFCNT_inc(value) : newSVsv(value); /* Refs shared, scalars copied */
1037              
1038 38535 100         if (t == SVt_PVHV) {
1039 18525 50         if (UNLIKELY(!hv_store((HV*)inner, final_key, final_key_len, copy, 0))) {
1040 0           SvREFCNT_dec(copy);
1041 0           croak("Failed to store value");
1042             }
1043 20010 50         } else if (t == SVt_PVAV) {
1044             IV idx;
1045 20010 100         if (!is_array_index(final_key, final_key_len, &idx)) {
1046 1           SvREFCNT_dec(copy);
1047 1           croak("Invalid array index");
1048             }
1049 20009 100         if (UNLIKELY(!av_store((AV*)inner, idx, copy))) {
1050 1           SvREFCNT_dec(copy);
1051 1           croak("Failed to store value");
1052             }
1053             } else {
1054 0           SvREFCNT_dec(copy);
1055 0           croak("Parent is not a hash or array");
1056             }
1057              
1058 38533 100         if (GIMME_V == G_VOID) {
1059 38532           XSRETURN_EMPTY;
1060             }
1061 1           RETVAL = SvREFCNT_inc(value);
1062             OUTPUT:
1063             RETVAL
1064              
1065             SV*
1066             path_delete(data, path)
1067             SV *data
1068             SV *path
1069             CODE:
1070             STRLEN path_len;
1071 26221           const char *path_str = SvPV(path, path_len);
1072             const char *final_key;
1073             STRLEN final_key_len;
1074              
1075 26221 100         if (path_len == 0) {
1076 2001           croak("Cannot delete root");
1077             }
1078              
1079 24220           SV *parent = navigate_to_parent(aTHX_ data, path_str, path_len, &final_key, &final_key_len, 0);
1080              
1081             /* final_key == NULL with parent means path refers to root - can't delete
1082             * final_key == NULL without parent means navigation failed - return undef */
1083 24220 100         if (!final_key) {
1084 2003 100         if (parent) {
1085 1           croak("Cannot delete root");
1086             }
1087 2002           RETVAL = &PL_sv_undef;
1088 22217 50         } else if (!parent || !SvROK(parent)) {
    50          
1089 0           RETVAL = &PL_sv_undef;
1090             } else {
1091 22217           SV *inner = SvRV(parent);
1092 22217           svtype t = SvTYPE(inner);
1093              
1094 22217 100         if (t == SVt_PVHV) {
1095 22212           RETVAL = hv_delete((HV*)inner, final_key, final_key_len, 0);
1096 22212 100         if (RETVAL) SvREFCNT_inc(RETVAL);
1097 4           else RETVAL = &PL_sv_undef;
1098 5 50         } else if (t == SVt_PVAV) {
1099             IV idx;
1100 5 50         if (is_array_index(final_key, final_key_len, &idx)) {
1101 5           SV *old = av_delete((AV*)inner, idx, 0);
1102 5 100         RETVAL = old ? SvREFCNT_inc(old) : &PL_sv_undef;
1103             } else {
1104 0           RETVAL = &PL_sv_undef;
1105             }
1106             } else {
1107 0           RETVAL = &PL_sv_undef;
1108             }
1109             }
1110             OUTPUT:
1111             RETVAL
1112              
1113             int
1114             path_exists(data, path)
1115             SV *data
1116             SV *path
1117             CODE:
1118             STRLEN path_len;
1119 2228           const char *path_str = SvPV(path, path_len);
1120             const char *final_key;
1121             STRLEN final_key_len;
1122              
1123 2228 100         if (path_len == 0) {
1124 4           RETVAL = 1;
1125             } else {
1126 2224           SV *parent = navigate_to_parent(aTHX_ data, path_str, path_len, &final_key, &final_key_len, 0);
1127              
1128             /* final_key == NULL with parent means path refers to root (e.g., "/" or "///") - always exists
1129             * final_key == NULL without parent means navigation failed (e.g., traverse non-ref) */
1130 2224 100         if (!final_key) {
1131 2002           RETVAL = parent ? 1 : 0;
1132 222 50         } else if (!parent || !SvROK(parent)) {
    50          
1133 0           RETVAL = 0;
1134             } else {
1135 222           SV *inner = SvRV(parent);
1136 222           svtype t = SvTYPE(inner);
1137              
1138 222 100         if (t == SVt_PVHV) {
1139 214           RETVAL = hv_exists((HV*)inner, final_key, final_key_len);
1140 8 50         } else if (t == SVt_PVAV) {
1141             IV idx;
1142 16           RETVAL = is_array_index(final_key, final_key_len, &idx)
1143 8 50         ? av_exists((AV*)inner, idx) : 0;
1144             } else {
1145 0           RETVAL = 0;
1146             }
1147             }
1148             }
1149             OUTPUT:
1150             RETVAL
1151              
1152             SV*
1153             patha_get(data, path_av)
1154             SV *data
1155             AV *path_av
1156             CODE:
1157 4055           SSize_t len = av_len(path_av) + 1;
1158 4055           SV *current = data;
1159              
1160 20154 100         for (SSize_t i = 0; i < len; i++) {
1161 16110           SV **key_ptr = av_fetch(path_av, i, 0);
1162 16110 50         if (UNLIKELY(!key_ptr || !*key_ptr || !SvROK(current))) {
    50          
    50          
    100          
1163 1           RETVAL = &PL_sv_undef;
1164 1           goto done;
1165             }
1166              
1167 16109           SV *inner = SvRV(current);
1168 16109           svtype t = SvTYPE(inner);
1169              
1170 16109 100         if (LIKELY(t == SVt_PVHV)) {
1171             STRLEN klen;
1172 16073           const char *kstr = SvPV(*key_ptr, klen);
1173 16073           SV **val = hv_fetch((HV*)inner, kstr, klen, 0);
1174 16073 100         if (UNLIKELY(!val || !*val)) { RETVAL = &PL_sv_undef; goto done; }
    50          
1175 16068           current = *val;
1176 36 50         } else if (t == SVt_PVAV) {
1177             IV idx;
1178 38 100         if (UNLIKELY(!sv_to_index(aTHX_ *key_ptr, &idx))) { RETVAL = &PL_sv_undef; goto done; }
1179 33           SV **val = av_fetch((AV*)inner, idx, 0);
1180 33 100         if (UNLIKELY(!val || !*val)) { RETVAL = &PL_sv_undef; goto done; }
    50          
1181 31           current = *val;
1182             } else {
1183 0           RETVAL = &PL_sv_undef;
1184 0           goto done;
1185             }
1186             }
1187 4044           RETVAL = SvREFCNT_inc(current);
1188 4055           done:
1189             OUTPUT:
1190             RETVAL
1191              
1192             SV*
1193             patha_set(data, path_av, value)
1194             SV *data
1195             AV *path_av
1196             SV *value
1197             CODE:
1198 4027           SSize_t len = av_len(path_av) + 1;
1199 4027           SV *current = data;
1200              
1201 4027 100         if (UNLIKELY(len == 0)) croak("Cannot set root");
1202              
1203 12054 100         for (SSize_t i = 0; i < len - 1; i++) {
1204 8029           SV **key_ptr = av_fetch(path_av, i, 0);
1205 8029 50         if (UNLIKELY(!key_ptr || !*key_ptr)) croak("Invalid path element");
    50          
1206 8029 50         if (UNLIKELY(!SvROK(current))) croak("Cannot navigate to path");
1207              
1208 8029           SV *inner = SvRV(current);
1209 8029           svtype t = SvTYPE(inner);
1210              
1211 8029 100         if (LIKELY(t == SVt_PVHV)) {
1212             STRLEN klen;
1213 8025           const char *kstr = SvPV(*key_ptr, klen);
1214 8025           SV **val = hv_fetch((HV*)inner, kstr, klen, 0);
1215 8025 100         if (UNLIKELY(!val || !*val || !SvROK(*val))) {
    50          
    100          
    100          
1216 4014           SV **next_key = av_fetch(path_av, i + 1, 0);
1217             IV dummy;
1218 4014 50         SV *new_ref = (next_key && *next_key && sv_to_index(aTHX_ *next_key, &dummy))
    100          
1219 5           ? newRV_noinc((SV*)newAV())
1220 8023 50         : newRV_noinc((SV*)newHV());
1221 4014 50         if (UNLIKELY(!hv_store((HV*)inner, kstr, klen, new_ref, 0))) {
1222 0           SvREFCNT_dec(new_ref);
1223 0           croak("Failed to store intermediate value");
1224             }
1225 4014           current = new_ref;
1226             } else {
1227 4011           current = *val;
1228             }
1229 4 50         } else if (t == SVt_PVAV) {
1230             IV idx;
1231 4 50         if (UNLIKELY(!sv_to_index(aTHX_ *key_ptr, &idx))) croak("Invalid array index");
1232 4           SV **val = av_fetch((AV*)inner, idx, 0);
1233 4 50         if (UNLIKELY(!val || !*val || !SvROK(*val))) {
    0          
    50          
    0          
1234 4           SV **next_key = av_fetch(path_av, i + 1, 0);
1235             IV dummy;
1236 4 50         SV *new_ref = (next_key && *next_key && sv_to_index(aTHX_ *next_key, &dummy))
    100          
1237 1           ? newRV_noinc((SV*)newAV())
1238 7 50         : newRV_noinc((SV*)newHV());
1239 4 50         if (UNLIKELY(!av_store((AV*)inner, idx, new_ref))) {
1240 0           SvREFCNT_dec(new_ref);
1241 0           croak("Failed to store intermediate value");
1242             }
1243 4           current = new_ref;
1244             } else {
1245 0           current = *val;
1246             }
1247             } else {
1248 0           croak("Cannot navigate to path");
1249             }
1250             }
1251              
1252 4025 50         if (UNLIKELY(!SvROK(current))) croak("Cannot set on non-reference");
1253 4025           SV *inner = SvRV(current);
1254 4025           svtype t = SvTYPE(inner);
1255 4025           SV **final_key_ptr = av_fetch(path_av, len - 1, 0);
1256 4025 50         if (UNLIKELY(!final_key_ptr || !*final_key_ptr)) croak("Invalid final key");
    50          
1257 4025 100         SV *copy = SvROK(value) ? SvREFCNT_inc(value) : newSVsv(value); /* Refs shared, scalars copied */
1258              
1259 4025 100         if (LIKELY(t == SVt_PVHV)) {
1260             STRLEN klen;
1261 4014           const char *kstr = SvPV(*final_key_ptr, klen);
1262 4014 50         if (UNLIKELY(!hv_store((HV*)inner, kstr, klen, copy, 0))) {
1263 0           SvREFCNT_dec(copy);
1264 0           croak("Failed to store value");
1265             }
1266 11 50         } else if (t == SVt_PVAV) {
1267             IV idx;
1268 11 50         if (UNLIKELY(!sv_to_index(aTHX_ *final_key_ptr, &idx))) {
1269 0           SvREFCNT_dec(copy);
1270 0           croak("Invalid array index");
1271             }
1272 11 100         if (UNLIKELY(!av_store((AV*)inner, idx, copy))) {
1273 1           SvREFCNT_dec(copy);
1274 1           croak("Failed to store value");
1275             }
1276             } else {
1277 0           SvREFCNT_dec(copy);
1278 0           croak("Parent is not a hash or array");
1279             }
1280              
1281 4024 100         if (GIMME_V == G_VOID) {
1282 4023           XSRETURN_EMPTY;
1283             }
1284 1           RETVAL = SvREFCNT_inc(value);
1285             OUTPUT:
1286             RETVAL
1287              
1288             int
1289             patha_exists(data, path_av)
1290             SV *data
1291             AV *path_av
1292             CODE:
1293 28           SSize_t len = av_len(path_av) + 1;
1294 28           SV *current = data;
1295              
1296 28 100         if (UNLIKELY(len == 0)) { RETVAL = 1; goto done; }
1297              
1298 50 100         for (SSize_t i = 0; i < len - 1; i++) {
1299 24           SV **key_ptr = av_fetch(path_av, i, 0);
1300 24 50         if (UNLIKELY(!key_ptr || !*key_ptr || !SvROK(current))) { RETVAL = 0; goto done; }
    50          
    50          
    50          
1301              
1302 24           SV *inner = SvRV(current);
1303 24           svtype t = SvTYPE(inner);
1304              
1305 24 100         if (LIKELY(t == SVt_PVHV)) {
1306             STRLEN klen;
1307 23           const char *kstr = SvPV(*key_ptr, klen);
1308 23           SV **val = hv_fetch((HV*)inner, kstr, klen, 0);
1309 23 50         if (UNLIKELY(!val || !*val)) { RETVAL = 0; goto done; }
    50          
1310 23           current = *val;
1311 1 50         } else if (t == SVt_PVAV) {
1312             IV idx;
1313 1 50         if (UNLIKELY(!sv_to_index(aTHX_ *key_ptr, &idx))) { RETVAL = 0; goto done; }
1314 1           SV **val = av_fetch((AV*)inner, idx, 0);
1315 1 50         if (UNLIKELY(!val || !*val)) { RETVAL = 0; goto done; }
    50          
1316 1           current = *val;
1317             } else {
1318 0           RETVAL = 0; goto done;
1319             }
1320             }
1321              
1322 26 50         if (UNLIKELY(!SvROK(current))) { RETVAL = 0; goto done; }
1323 26           SV *inner = SvRV(current);
1324 26           svtype t = SvTYPE(inner);
1325 26           SV **final_key_ptr = av_fetch(path_av, len - 1, 0);
1326 26 50         if (UNLIKELY(!final_key_ptr || !*final_key_ptr)) { RETVAL = 0; goto done; }
    50          
1327              
1328 26 100         if (LIKELY(t == SVt_PVHV)) {
1329             STRLEN klen;
1330 12           const char *kstr = SvPV(*final_key_ptr, klen);
1331 12           RETVAL = hv_exists((HV*)inner, kstr, klen);
1332 14 50         } else if (t == SVt_PVAV) {
1333             IV idx;
1334 14 50         RETVAL = sv_to_index(aTHX_ *final_key_ptr, &idx) ? av_exists((AV*)inner, idx) : 0;
1335             } else {
1336 0           RETVAL = 0;
1337             }
1338 28 100         done:
1339             OUTPUT:
1340             RETVAL
1341              
1342             SV*
1343             patha_delete(data, path_av)
1344             SV *data
1345             AV *path_av
1346             CODE:
1347 4017           SSize_t len = av_len(path_av) + 1;
1348 4017           SV *current = data;
1349              
1350 4017 100         if (UNLIKELY(len == 0)) croak("Cannot delete root");
1351              
1352 12025 100         for (SSize_t i = 0; i < len - 1; i++) {
1353 8011           SV **key_ptr = av_fetch(path_av, i, 0);
1354 8011 50         if (UNLIKELY(!key_ptr || !*key_ptr || !SvROK(current))) { RETVAL = &PL_sv_undef; goto done; }
    50          
    50          
    100          
1355              
1356 8010           SV *inner = SvRV(current);
1357 8010           svtype t = SvTYPE(inner);
1358              
1359 8010 50         if (LIKELY(t == SVt_PVHV)) {
1360             STRLEN klen;
1361 8010           const char *kstr = SvPV(*key_ptr, klen);
1362 8010           SV **val = hv_fetch((HV*)inner, kstr, klen, 0);
1363 8010 50         if (UNLIKELY(!val || !*val)) { RETVAL = &PL_sv_undef; goto done; }
    50          
1364 8010           current = *val;
1365 0 0         } else if (t == SVt_PVAV) {
1366             IV idx;
1367 0 0         if (UNLIKELY(!sv_to_index(aTHX_ *key_ptr, &idx))) { RETVAL = &PL_sv_undef; goto done; }
1368 0           SV **val = av_fetch((AV*)inner, idx, 0);
1369 0 0         if (UNLIKELY(!val || !*val)) { RETVAL = &PL_sv_undef; goto done; }
    0          
1370 0           current = *val;
1371             } else {
1372 0           RETVAL = &PL_sv_undef; goto done;
1373             }
1374             }
1375              
1376 4014 50         if (UNLIKELY(!SvROK(current))) { RETVAL = &PL_sv_undef; goto done; }
1377 4014           SV *inner = SvRV(current);
1378 4014           svtype t = SvTYPE(inner);
1379 4014           SV **final_key_ptr = av_fetch(path_av, len - 1, 0);
1380 4014 50         if (UNLIKELY(!final_key_ptr || !*final_key_ptr)) { RETVAL = &PL_sv_undef; goto done; }
    50          
1381              
1382 4014 100         if (LIKELY(t == SVt_PVHV)) {
1383             STRLEN klen;
1384 4011           const char *kstr = SvPV(*final_key_ptr, klen);
1385 4011           RETVAL = hv_delete((HV*)inner, kstr, klen, 0);
1386 4011 100         if (RETVAL) SvREFCNT_inc(RETVAL);
1387 3           else RETVAL = &PL_sv_undef;
1388 3 50         } else if (t == SVt_PVAV) {
1389             IV idx;
1390 3 50         if (sv_to_index(aTHX_ *final_key_ptr, &idx)) {
1391 3           SV *old = av_delete((AV*)inner, idx, 0);
1392 3 50         RETVAL = old ? SvREFCNT_inc(old) : &PL_sv_undef;
1393             } else {
1394 0           RETVAL = &PL_sv_undef;
1395             }
1396             } else {
1397 0           RETVAL = &PL_sv_undef;
1398             }
1399 4015           done:
1400             OUTPUT:
1401             RETVAL
1402              
1403             # Compiled path API
1404              
1405             SV*
1406             path_compile(path)
1407             SV *path
1408             CODE:
1409 48           CompiledPath *cp = compile_path(aTHX_ path);
1410 48           SV *obj = newSV(0);
1411 48           sv_setref_pv(obj, "Data::Path::XS::Compiled", (void*)cp);
1412 48           RETVAL = obj;
1413             OUTPUT:
1414             RETVAL
1415              
1416             SV*
1417             pathc_get(data, compiled)
1418             SV *data
1419             SV *compiled
1420             CODE:
1421             CompiledPath *cp;
1422 226 100         VALIDATE_COMPILED_PATH(compiled, cp);
    50          
    50          
1423              
1424 225 100         if (UNLIKELY(cp->count == 0)) {
1425 1           RETVAL = SvREFCNT_inc(data);
1426             } else {
1427             /* Fully inlined navigation for maximum speed */
1428 224           SV *current = data;
1429 224           PathComponent *c = cp->components;
1430 224           PathComponent *end = c + cp->count;
1431              
1432 681 100         while (c < end) {
1433 459 100         if (UNLIKELY(!SvROK(current))) {
1434 1           RETVAL = &PL_sv_undef;
1435 1           goto done;
1436             }
1437              
1438 458           SV *inner = SvRV(current);
1439 458           svtype t = SvTYPE(inner);
1440              
1441 458 100         if (LIKELY(t == SVt_PVHV)) {
1442 448           SV **val = hv_fetch((HV*)inner, c->str, c->len, 0);
1443 448 100         if (UNLIKELY(!val || !*val)) {
    50          
1444 1           RETVAL = &PL_sv_undef;
1445 1           goto done;
1446             }
1447 447           current = *val;
1448 10 50         } else if (t == SVt_PVAV) {
1449 10 50         if (UNLIKELY(!c->is_numeric)) {
1450 0           RETVAL = &PL_sv_undef;
1451 0           goto done;
1452             }
1453 10           SV **val = av_fetch((AV*)inner, c->idx, 0);
1454 10 50         if (UNLIKELY(!val || !*val)) {
    50          
1455 0           RETVAL = &PL_sv_undef;
1456 0           goto done;
1457             }
1458 10           current = *val;
1459             } else {
1460 0           RETVAL = &PL_sv_undef;
1461 0           goto done;
1462             }
1463 457           c++;
1464             }
1465 222           RETVAL = SvREFCNT_inc(current);
1466             }
1467 225           done:
1468             OUTPUT:
1469             RETVAL
1470              
1471             SV*
1472             pathc_set(data, compiled, value)
1473             SV *data
1474             SV *compiled
1475             SV *value
1476             CODE:
1477             CompiledPath *cp;
1478 212 50         VALIDATE_COMPILED_PATH(compiled, cp);
    50          
    50          
1479              
1480 212 100         if (UNLIKELY(cp->count == 0)) croak("Cannot set root");
1481              
1482             /* Fully inlined navigation with creation for maximum speed */
1483 211           SV *current = data;
1484 211           PathComponent *c = cp->components;
1485 211           PathComponent *last = c + cp->count - 1;
1486              
1487             /* Navigate to parent, creating intermediate structures as needed */
1488 425 100         while (c < last) {
1489 215 50         if (UNLIKELY(!SvROK(current))) croak("Cannot navigate to path");
1490              
1491 215           SV *inner = SvRV(current);
1492 215           svtype t = SvTYPE(inner);
1493              
1494 215 100         if (LIKELY(t == SVt_PVHV)) {
1495 213           SV **val = hv_fetch((HV*)inner, c->str, c->len, 0);
1496 213 100         if (UNLIKELY(!val || !*val || !SvROK(*val))) {
    50          
    100          
    100          
1497             /* Create intermediate structure using pre-computed type */
1498 16           SV *new_ref = c->next_is_array
1499 1           ? newRV_noinc((SV*)newAV())
1500 8 100         : newRV_noinc((SV*)newHV());
1501 8 50         if (UNLIKELY(!hv_store((HV*)inner, c->str, c->len, new_ref, 0))) {
1502 0           SvREFCNT_dec(new_ref);
1503 0           croak("Failed to store intermediate value");
1504             }
1505 8           current = new_ref;
1506             } else {
1507 205           current = *val;
1508             }
1509 2 50         } else if (t == SVt_PVAV) {
1510 2 100         if (UNLIKELY(!c->is_numeric)) croak("Invalid array index");
1511 1           SV **val = av_fetch((AV*)inner, c->idx, 0);
1512 1 50         if (UNLIKELY(!val || !*val || !SvROK(*val))) {
    0          
    50          
    0          
1513 2           SV *new_ref = c->next_is_array
1514 0           ? newRV_noinc((SV*)newAV())
1515 1 50         : newRV_noinc((SV*)newHV());
1516 1 50         if (UNLIKELY(!av_store((AV*)inner, c->idx, new_ref))) {
1517 0           SvREFCNT_dec(new_ref);
1518 0           croak("Failed to store intermediate value");
1519             }
1520 1           current = new_ref;
1521             } else {
1522 0           current = *val;
1523             }
1524             } else {
1525 0           croak("Cannot navigate to path");
1526             }
1527 214           c++;
1528             }
1529              
1530             /* Set the final value */
1531 210 50         if (UNLIKELY(!SvROK(current))) croak("Cannot navigate to path");
1532 210           SV *inner = SvRV(current);
1533 210           svtype t = SvTYPE(inner);
1534 210 50         SV *copy = SvROK(value) ? SvREFCNT_inc(value) : newSVsv(value); /* Refs shared, scalars copied */
1535              
1536 210 100         if (LIKELY(t == SVt_PVHV)) {
1537 208 50         if (UNLIKELY(!hv_store((HV*)inner, last->str, last->len, copy, 0))) {
1538 0           SvREFCNT_dec(copy);
1539 0           croak("Failed to store value");
1540             }
1541 2 50         } else if (t == SVt_PVAV) {
1542 2 50         if (UNLIKELY(!last->is_numeric)) { SvREFCNT_dec(copy); croak("Invalid array index"); }
1543 2 100         if (UNLIKELY(!av_store((AV*)inner, last->idx, copy))) {
1544 1           SvREFCNT_dec(copy);
1545 1           croak("Failed to store value");
1546             }
1547             } else {
1548 0           SvREFCNT_dec(copy);
1549 0           croak("Parent is not a hash or array");
1550             }
1551              
1552             /* Skip return value overhead in void context */
1553 209 100         if (GIMME_V == G_VOID) {
1554 208           XSRETURN_EMPTY;
1555             }
1556 1           RETVAL = SvREFCNT_inc(value);
1557             OUTPUT:
1558             RETVAL
1559              
1560             int
1561             pathc_exists(data, compiled)
1562             SV *data
1563             SV *compiled
1564             CODE:
1565             CompiledPath *cp;
1566 11 50         VALIDATE_COMPILED_PATH(compiled, cp);
    50          
    50          
1567              
1568 11 100         if (UNLIKELY(cp->count == 0)) {
1569 1           RETVAL = 1;
1570             } else {
1571             /* Inlined navigation to parent */
1572 10           SV *current = data;
1573 10           PathComponent *c = cp->components;
1574 10           PathComponent *last = c + cp->count - 1;
1575              
1576 20 100         while (c < last) {
1577 10 50         if (UNLIKELY(!SvROK(current))) {
1578 0           RETVAL = 0;
1579 0           goto done;
1580             }
1581 10           SV *inner = SvRV(current);
1582 10           svtype t = SvTYPE(inner);
1583              
1584 10 50         if (LIKELY(t == SVt_PVHV)) {
1585 10           SV **val = hv_fetch((HV*)inner, c->str, c->len, 0);
1586 10 50         if (UNLIKELY(!val || !*val)) { RETVAL = 0; goto done; }
    50          
1587 10           current = *val;
1588 0 0         } else if (t == SVt_PVAV) {
1589 0 0         if (UNLIKELY(!c->is_numeric)) { RETVAL = 0; goto done; }
1590 0           SV **val = av_fetch((AV*)inner, c->idx, 0);
1591 0 0         if (UNLIKELY(!val || !*val)) { RETVAL = 0; goto done; }
    0          
1592 0           current = *val;
1593             } else {
1594 0           RETVAL = 0;
1595 0           goto done;
1596             }
1597 10           c++;
1598             }
1599              
1600             /* Check final key existence */
1601 10 50         if (UNLIKELY(!SvROK(current))) {
1602 0           RETVAL = 0;
1603             } else {
1604 10           SV *inner = SvRV(current);
1605 10           svtype t = SvTYPE(inner);
1606 10 100         if (LIKELY(t == SVt_PVHV)) {
1607 5           RETVAL = hv_exists((HV*)inner, last->str, last->len);
1608 5 50         } else if (t == SVt_PVAV) {
1609 5 50         RETVAL = last->is_numeric ? av_exists((AV*)inner, last->idx) : 0;
1610             } else {
1611 0           RETVAL = 0;
1612             }
1613             }
1614             }
1615 11 100         done:
1616             OUTPUT:
1617             RETVAL
1618              
1619             SV*
1620             pathc_delete(data, compiled)
1621             SV *data
1622             SV *compiled
1623             CODE:
1624             CompiledPath *cp;
1625 6 50         VALIDATE_COMPILED_PATH(compiled, cp);
    50          
    50          
1626              
1627 6 100         if (UNLIKELY(cp->count == 0)) croak("Cannot delete root");
1628              
1629             /* Inlined navigation to parent */
1630 5           SV *current = data;
1631 5           PathComponent *c = cp->components;
1632 5           PathComponent *last = c + cp->count - 1;
1633              
1634 10 100         while (c < last) {
1635 5 50         if (UNLIKELY(!SvROK(current))) {
1636 0           RETVAL = &PL_sv_undef;
1637 0           goto done;
1638             }
1639 5           SV *inner = SvRV(current);
1640 5           svtype t = SvTYPE(inner);
1641              
1642 5 50         if (LIKELY(t == SVt_PVHV)) {
1643 5           SV **val = hv_fetch((HV*)inner, c->str, c->len, 0);
1644 5 50         if (UNLIKELY(!val || !*val)) { RETVAL = &PL_sv_undef; goto done; }
    50          
1645 5           current = *val;
1646 0 0         } else if (t == SVt_PVAV) {
1647 0 0         if (UNLIKELY(!c->is_numeric)) { RETVAL = &PL_sv_undef; goto done; }
1648 0           SV **val = av_fetch((AV*)inner, c->idx, 0);
1649 0 0         if (UNLIKELY(!val || !*val)) { RETVAL = &PL_sv_undef; goto done; }
    0          
1650 0           current = *val;
1651             } else {
1652 0           RETVAL = &PL_sv_undef;
1653 0           goto done;
1654             }
1655 5           c++;
1656             }
1657              
1658             /* Delete final key */
1659 5 50         if (UNLIKELY(!SvROK(current))) {
1660 0           RETVAL = &PL_sv_undef;
1661             } else {
1662 5           SV *inner = SvRV(current);
1663 5           svtype t = SvTYPE(inner);
1664              
1665 5 100         if (LIKELY(t == SVt_PVHV)) {
1666 4           RETVAL = hv_delete((HV*)inner, last->str, last->len, 0);
1667 4 100         if (RETVAL) SvREFCNT_inc(RETVAL);
1668 1           else RETVAL = &PL_sv_undef;
1669 1 50         } else if (t == SVt_PVAV) {
1670 1 50         if (last->is_numeric) {
1671 1           SV *old = av_delete((AV*)inner, last->idx, 0);
1672 1 50         RETVAL = old ? SvREFCNT_inc(old) : &PL_sv_undef;
1673             } else {
1674 0           RETVAL = &PL_sv_undef;
1675             }
1676             } else {
1677 0           RETVAL = &PL_sv_undef;
1678             }
1679             }
1680 5           done:
1681             OUTPUT:
1682             RETVAL
1683              
1684             MODULE = Data::Path::XS PACKAGE = Data::Path::XS::Compiled
1685              
1686             void
1687             DESTROY(self)
1688             SV *self
1689             CODE:
1690 48 50         if (SvROK(self)) {
1691 48           CompiledPath *cp = INT2PTR(CompiledPath*, SvIV(SvRV(self)));
1692             /* Validate magic before freeing to prevent crashes from invalid objects */
1693 48 50         if (cp && cp->magic == COMPILED_PATH_MAGIC) {
    50          
1694 48           free_compiled_path(aTHX_ cp);
1695             }
1696             }
1697              
1698             MODULE = Data::Path::XS PACKAGE = Data::Path::XS
1699              
1700             BOOT:
1701             {
1702             /* Register custom ops for dynamic paths */
1703 12           XopENTRY_set(&xop_pathget, xop_name, "pathget_dynamic");
1704 12           XopENTRY_set(&xop_pathget, xop_desc, "dynamic path get");
1705 12           Perl_custom_op_register(aTHX_ pp_pathget_dynamic, &xop_pathget);
1706              
1707 12           XopENTRY_set(&xop_pathset, xop_name, "pathset_dynamic");
1708 12           XopENTRY_set(&xop_pathset, xop_desc, "dynamic path set");
1709 12           Perl_custom_op_register(aTHX_ pp_pathset_dynamic, &xop_pathset);
1710              
1711 12           XopENTRY_set(&xop_pathdelete, xop_name, "pathdelete_dynamic");
1712 12           XopENTRY_set(&xop_pathdelete, xop_desc, "dynamic path delete");
1713 12           Perl_custom_op_register(aTHX_ pp_pathdelete_dynamic, &xop_pathdelete);
1714              
1715 12           XopENTRY_set(&xop_pathexists, xop_name, "pathexists_dynamic");
1716 12           XopENTRY_set(&xop_pathexists, xop_desc, "dynamic path exists");
1717 12           Perl_custom_op_register(aTHX_ pp_pathexists_dynamic, &xop_pathexists);
1718              
1719 12           boot_xs_parse_keyword(0.40);
1720 12           register_xs_parse_keyword("pathget", &hooks_pathget, NULL);
1721 12           register_xs_parse_keyword("pathset", &hooks_pathset, NULL);
1722 12           register_xs_parse_keyword("pathdelete", &hooks_pathdelete, NULL);
1723 12           register_xs_parse_keyword("pathexists", &hooks_pathexists, NULL);
1724             }