File Coverage

XS.xs
Criterion Covered Total %
statement 752 870 86.4
branch 567 794 71.4
condition n/a
subroutine n/a
pod n/a
total 1319 1664 79.2


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