File Coverage

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