File Coverage

Sekhmet.xs
Criterion Covered Total %
statement 164 239 68.6
branch 48 128 37.5
condition n/a
subroutine n/a
pod n/a
total 212 367 57.7


line stmt bran cond sub pod time code
1             #include "sekhmet.h"
2              
3             /* ══════════════════════════════════════════════════════════════════
4             * Custom ops - bypass XS subroutine dispatch overhead (5.14+)
5             * ══════════════════════════════════════════════════════════════════ */
6              
7             #if PERL_VERSION >= 14
8              
9             /* ── Macro: generate ck_* check function ───────────────────────── */
10              
11             #define SEKHMET_CK(name) \
12             static OP *sekhmet_ck_##name(pTHX_ OP *o, GV *namegv, SV *protosv) { \
13             PERL_UNUSED_ARG(namegv); PERL_UNUSED_ARG(protosv); \
14             o->op_ppaddr = pp_sekhmet_##name; return o; \
15             }
16              
17             /* ── XOP descriptors ───────────────────────────────────────────── */
18              
19             static XOP sekhmet_xop_ulid, sekhmet_xop_ulid_binary,
20             sekhmet_xop_ulid_monotonic, sekhmet_xop_ulid_monotonic_binary,
21             sekhmet_xop_ulid_time, sekhmet_xop_ulid_time_ms,
22             sekhmet_xop_ulid_to_uuid, sekhmet_xop_uuid_to_ulid,
23             sekhmet_xop_ulid_compare, sekhmet_xop_ulid_validate;
24              
25             /* ── pp_* : Generator ops (no args) ────────────────────────────── */
26              
27             /* ulid() → 26-char Crockford string */
28 1121           static OP *pp_sekhmet_ulid(pTHX) {
29 1121           dSP;
30 1121           I32 markix = POPMARK;
31             unsigned char bin[16];
32             char str[27];
33              
34 1121           sekhmet_ulid_generate(bin);
35 1121           sekhmet_ulid_encode(str, bin);
36 1121           str[26] = '\0';
37              
38 1121           SP = PL_stack_base + markix;
39 1121 50         XPUSHs(sv_2mortal(newSVpvn(str, 26)));
40 1121           PUTBACK;
41 1121           return NORMAL;
42             }
43 17           SEKHMET_CK(ulid)
44              
45             /* ulid_binary() → 16-byte raw SV */
46 11           static OP *pp_sekhmet_ulid_binary(pTHX) {
47 11           dSP;
48 11           I32 markix = POPMARK;
49             unsigned char bin[16];
50              
51 11           sekhmet_ulid_generate(bin);
52              
53 11           SP = PL_stack_base + markix;
54 11 50         XPUSHs(sv_2mortal(newSVpvn((const char *)bin, 16)));
55 11           PUTBACK;
56 11           return NORMAL;
57             }
58 7           SEKHMET_CK(ulid_binary)
59              
60             /* ulid_monotonic() → 26-char Crockford string (uses MY_CXT) */
61 1730           static OP *pp_sekhmet_ulid_monotonic(pTHX) {
62 1730           dSP;
63             dMY_CXT;
64 1730           I32 markix = POPMARK;
65             unsigned char bin[16];
66             char str[27];
67              
68 1730           sekhmet_ulid_monotonic(bin, &MY_CXT.mono_state);
69 1730           sekhmet_ulid_encode(str, bin);
70 1730           str[26] = '\0';
71              
72 1730           SP = PL_stack_base + markix;
73 1730 50         XPUSHs(sv_2mortal(newSVpvn(str, 26)));
74 1730           PUTBACK;
75 1730           return NORMAL;
76             }
77 16           SEKHMET_CK(ulid_monotonic)
78              
79             /* ulid_monotonic_binary() → 16-byte raw SV (uses MY_CXT) */
80 52           static OP *pp_sekhmet_ulid_monotonic_binary(pTHX) {
81 52           dSP;
82             dMY_CXT;
83 52           I32 markix = POPMARK;
84             unsigned char bin[16];
85              
86 52           sekhmet_ulid_monotonic(bin, &MY_CXT.mono_state);
87              
88 52           SP = PL_stack_base + markix;
89 52 50         XPUSHs(sv_2mortal(newSVpvn((const char *)bin, 16)));
90 52           PUTBACK;
91 52           return NORMAL;
92             }
93 3           SEKHMET_CK(ulid_monotonic_binary)
94              
95             /* ── pp_* : Utility ops (1-2 args) ─────────────────────────────── */
96              
97             /* ulid_time(ulid) → NV epoch seconds */
98 4           static OP *pp_sekhmet_ulid_time(pTHX) {
99 4           dSP;
100 4           I32 markix = POPMARK;
101 4           I32 ax = markix + 1;
102 4           I32 items = SP - PL_stack_base - markix - 1;
103             unsigned char bin[16];
104             STRLEN in_len;
105             const char *in_str;
106              
107 4 50         if (items < 1) croak("ulid_time requires 1 argument");
108              
109 4           in_str = SvPV(PL_stack_base[ax], in_len);
110              
111 4 100         if (in_len == 16) {
112 1           memcpy(bin, in_str, 16);
113 3 50         } else if (in_len == 26) {
114 3 50         if (!sekhmet_ulid_decode(bin, in_str, 26))
115 0           croak("Sekhmet: invalid ULID string");
116             } else {
117 0           croak("Sekhmet: ulid_time expects 16-byte binary or 26-char string");
118             }
119              
120 4           SP = PL_stack_base + markix;
121 4 50         XPUSHs(sv_2mortal(newSVnv(sekhmet_ulid_time(bin))));
122 4           PUTBACK;
123 4           return NORMAL;
124             }
125 4           SEKHMET_CK(ulid_time)
126              
127             /* ulid_time_ms(ulid) → IV epoch milliseconds */
128 14           static OP *pp_sekhmet_ulid_time_ms(pTHX) {
129 14           dSP;
130 14           I32 markix = POPMARK;
131 14           I32 ax = markix + 1;
132 14           I32 items = SP - PL_stack_base - markix - 1;
133             unsigned char bin[16];
134             STRLEN in_len;
135             const char *in_str;
136              
137 14 50         if (items < 1) croak("ulid_time_ms requires 1 argument");
138              
139 14           in_str = SvPV(PL_stack_base[ax], in_len);
140              
141 14 100         if (in_len == 16) {
142 1           memcpy(bin, in_str, 16);
143 13 50         } else if (in_len == 26) {
144 13 50         if (!sekhmet_ulid_decode(bin, in_str, 26))
145 0           croak("Sekhmet: invalid ULID string");
146             } else {
147 0           croak("Sekhmet: ulid_time_ms expects 16-byte binary or 26-char string");
148             }
149              
150 14           SP = PL_stack_base + markix;
151             {
152 14           uint64_t ms = sekhmet_ulid_time_ms(bin);
153             /* Use NV for large ms values that exceed IV range on 32-bit */
154 14 50         if (ms > (uint64_t)IV_MAX)
155 0 0         XPUSHs(sv_2mortal(newSVnv((NV)ms)));
156             else
157 14 50         XPUSHs(sv_2mortal(newSViv((IV)ms)));
158             }
159 14           PUTBACK;
160 14           return NORMAL;
161             }
162 10           SEKHMET_CK(ulid_time_ms)
163              
164             /* ulid_to_uuid(ulid) → UUID string */
165 8           static OP *pp_sekhmet_ulid_to_uuid(pTHX) {
166 8           dSP;
167 8           I32 markix = POPMARK;
168 8           I32 ax = markix + 1;
169 8           I32 items = SP - PL_stack_base - markix - 1;
170             unsigned char bin[16];
171             char uuid_str[37];
172             STRLEN in_len;
173             const char *in_str;
174              
175 8 50         if (items < 1) croak("ulid_to_uuid requires 1 argument");
176              
177 8           in_str = SvPV(PL_stack_base[ax], in_len);
178              
179 8 100         if (in_len == 16) {
180 1           memcpy(bin, in_str, 16);
181 7 50         } else if (in_len == 26) {
182 7 50         if (!sekhmet_ulid_decode(bin, in_str, 26))
183 0           croak("Sekhmet: invalid ULID string");
184             } else {
185 0           croak("Sekhmet: ulid_to_uuid expects 16-byte binary or 26-char string");
186             }
187              
188 8           sekhmet_ulid_to_uuid_str(uuid_str, bin);
189 8           uuid_str[36] = '\0';
190              
191 8           SP = PL_stack_base + markix;
192 8 50         XPUSHs(sv_2mortal(newSVpvn(uuid_str, 36)));
193 8           PUTBACK;
194 8           return NORMAL;
195             }
196 6           SEKHMET_CK(ulid_to_uuid)
197              
198             /* uuid_to_ulid(uuid_string) → 26-char ULID */
199 7           static OP *pp_sekhmet_uuid_to_ulid(pTHX) {
200 7           dSP;
201 7           I32 markix = POPMARK;
202 7           I32 ax = markix + 1;
203 7           I32 items = SP - PL_stack_base - markix - 1;
204             unsigned char bin[16];
205             char str[27];
206             STRLEN in_len;
207             const char *in_str;
208              
209 7 50         if (items < 1) croak("uuid_to_ulid requires 1 argument");
210              
211 7           in_str = SvPV(PL_stack_base[ax], in_len);
212              
213 7 50         if (!sekhmet_uuid_to_ulid_bin(bin, in_str, (int)in_len))
214 0           croak("Sekhmet: cannot parse UUID string");
215              
216 7           sekhmet_ulid_encode(str, bin);
217 7           str[26] = '\0';
218              
219 7           SP = PL_stack_base + markix;
220 7 50         XPUSHs(sv_2mortal(newSVpvn(str, 26)));
221 7           PUTBACK;
222 7           return NORMAL;
223             }
224 5           SEKHMET_CK(uuid_to_ulid)
225              
226             /* ulid_compare(a, b) → -1/0/1 */
227 42           static OP *pp_sekhmet_ulid_compare(pTHX) {
228 42           dSP;
229 42           I32 markix = POPMARK;
230 42           I32 ax = markix + 1;
231 42           I32 items = SP - PL_stack_base - markix - 1;
232             unsigned char a[16], b[16];
233             STRLEN a_len, b_len;
234             const char *a_str, *b_str;
235             int cmp;
236              
237 42 50         if (items < 2) croak("ulid_compare requires 2 arguments");
238              
239 42           a_str = SvPV(PL_stack_base[ax], a_len);
240 42           b_str = SvPV(PL_stack_base[ax + 1], b_len);
241              
242             /* Decode first ULID */
243 42 50         if (a_len == 16) memcpy(a, a_str, 16);
244 42 50         else if (a_len == 26) {
245 42 50         if (!sekhmet_ulid_decode(a, a_str, 26))
246 0           croak("Sekhmet: invalid first ULID");
247 0           } else croak("Sekhmet: ulid_compare expects 16-byte binary or 26-char string");
248              
249             /* Decode second ULID */
250 42 50         if (b_len == 16) memcpy(b, b_str, 16);
251 42 50         else if (b_len == 26) {
252 42 50         if (!sekhmet_ulid_decode(b, b_str, 26))
253 0           croak("Sekhmet: invalid second ULID");
254 0           } else croak("Sekhmet: ulid_compare expects 16-byte binary or 26-char string");
255              
256 42           cmp = sekhmet_ulid_compare(a, b);
257              
258 42           SP = PL_stack_base + markix;
259 42 50         XPUSHs(sv_2mortal(newSViv((cmp < 0) ? -1 : (cmp > 0) ? 1 : 0)));
    100          
260 42           PUTBACK;
261 42           return NORMAL;
262             }
263 16           SEKHMET_CK(ulid_compare)
264              
265             /* ulid_validate(str) → 1/0 */
266 128           static OP *pp_sekhmet_ulid_validate(pTHX) {
267 128           dSP;
268 128           I32 markix = POPMARK;
269 128           I32 ax = markix + 1;
270 128           I32 items = SP - PL_stack_base - markix - 1;
271             STRLEN in_len;
272             const char *in_str;
273              
274 128 50         if (items < 1) croak("ulid_validate requires 1 argument");
275              
276 128           in_str = SvPV(PL_stack_base[ax], in_len);
277              
278 128           SP = PL_stack_base + markix;
279 128 50         XPUSHs(sv_2mortal(newSViv(sekhmet_ulid_validate(in_str, (int)in_len))));
280 128           PUTBACK;
281 128           return NORMAL;
282             }
283 25           SEKHMET_CK(ulid_validate)
284              
285             /* ── Registration macros ───────────────────────────────────────── */
286              
287             #define SEKHMET_REG_XOP(c_name, desc) \
288             XopENTRY_set(&sekhmet_xop_##c_name, xop_name, "sekhmet_" #c_name); \
289             XopENTRY_set(&sekhmet_xop_##c_name, xop_desc, desc); \
290             Perl_custom_op_register(aTHX_ pp_sekhmet_##c_name, &sekhmet_xop_##c_name);
291              
292             #define SEKHMET_REG_CK(perl_name, c_name) { \
293             CV *cv = get_cv("Sekhmet::" perl_name, 0); \
294             if (cv) cv_set_call_checker(cv, sekhmet_ck_##c_name, (SV *)cv); \
295             }
296              
297 9           static void sekhmet_register_custom_ops(pTHX) {
298 9           SEKHMET_REG_XOP(ulid, "generate ULID string")
299 9           SEKHMET_REG_XOP(ulid_binary, "generate ULID binary")
300 9           SEKHMET_REG_XOP(ulid_monotonic, "generate monotonic ULID string")
301 9           SEKHMET_REG_XOP(ulid_monotonic_binary, "generate monotonic ULID binary")
302 9           SEKHMET_REG_XOP(ulid_time, "extract ULID timestamp seconds")
303 9           SEKHMET_REG_XOP(ulid_time_ms, "extract ULID timestamp ms")
304 9           SEKHMET_REG_XOP(ulid_to_uuid, "convert ULID to UUID")
305 9           SEKHMET_REG_XOP(uuid_to_ulid, "convert UUID to ULID")
306 9           SEKHMET_REG_XOP(ulid_compare, "compare two ULIDs")
307 9           SEKHMET_REG_XOP(ulid_validate, "validate ULID string")
308              
309 9 50         SEKHMET_REG_CK("ulid", ulid)
310 9 50         SEKHMET_REG_CK("ulid_binary", ulid_binary)
311 9 50         SEKHMET_REG_CK("ulid_monotonic", ulid_monotonic)
312 9 50         SEKHMET_REG_CK("ulid_monotonic_binary", ulid_monotonic_binary)
313 9 50         SEKHMET_REG_CK("ulid_time", ulid_time)
314 9 50         SEKHMET_REG_CK("ulid_time_ms", ulid_time_ms)
315 9 50         SEKHMET_REG_CK("ulid_to_uuid", ulid_to_uuid)
316 9 50         SEKHMET_REG_CK("uuid_to_ulid", uuid_to_ulid)
317 9 50         SEKHMET_REG_CK("ulid_compare", ulid_compare)
318 9 50         SEKHMET_REG_CK("ulid_validate", ulid_validate)
319 9           }
320              
321             #endif /* PERL_VERSION >= 14 */
322              
323             /* ══════════════════════════════════════════════════════════════════
324             * XS module definition - XSUBs serve as fallbacks for Perl < 5.14
325             * ══════════════════════════════════════════════════════════════════ */
326              
327             MODULE = Sekhmet PACKAGE = Sekhmet
328              
329             BOOT:
330             {
331             MY_CXT_INIT;
332 9           memset(&MY_CXT.mono_state, 0, sizeof(sekhmet_monotonic_state_t));
333 9           horus_pool_refill();
334             #if PERL_VERSION >= 14
335 9           sekhmet_register_custom_ops(aTHX);
336             #endif
337             }
338              
339             #ifdef USE_ITHREADS
340              
341             void
342             CLONE(...)
343             CODE:
344             MY_CXT_CLONE;
345             memset(&MY_CXT.mono_state, 0, sizeof(sekhmet_monotonic_state_t));
346              
347             #endif
348              
349             SV *
350             ulid()
351             CODE:
352             {
353             unsigned char bin[16];
354             char str[27];
355 0           sekhmet_ulid_generate(bin);
356 0           sekhmet_ulid_encode(str, bin);
357 0           str[26] = '\0';
358 0           RETVAL = newSVpvn(str, 26);
359             }
360             OUTPUT:
361             RETVAL
362              
363             SV *
364             ulid_binary()
365             CODE:
366             {
367             unsigned char bin[16];
368 0           sekhmet_ulid_generate(bin);
369 0           RETVAL = newSVpvn((const char *)bin, 16);
370             }
371             OUTPUT:
372             RETVAL
373              
374             SV *
375             ulid_monotonic()
376             CODE:
377             {
378             dMY_CXT;
379             unsigned char bin[16];
380             char str[27];
381 0           sekhmet_ulid_monotonic(bin, &MY_CXT.mono_state);
382 0           sekhmet_ulid_encode(str, bin);
383 0           str[26] = '\0';
384 0           RETVAL = newSVpvn(str, 26);
385             }
386             OUTPUT:
387             RETVAL
388              
389             SV *
390             ulid_monotonic_binary()
391             CODE:
392             {
393             dMY_CXT;
394             unsigned char bin[16];
395 0           sekhmet_ulid_monotonic(bin, &MY_CXT.mono_state);
396 0           RETVAL = newSVpvn((const char *)bin, 16);
397             }
398             OUTPUT:
399             RETVAL
400              
401             NV
402             ulid_time(input)
403             SV *input
404             CODE:
405             {
406             unsigned char bin[16];
407             STRLEN in_len;
408 0           const char *in_str = SvPV(input, in_len);
409              
410 0 0         if (in_len == 16) {
411 0           memcpy(bin, in_str, 16);
412 0 0         } else if (in_len == 26) {
413 0 0         if (!sekhmet_ulid_decode(bin, in_str, 26))
414 0           croak("Sekhmet: invalid ULID string");
415             } else {
416 0           croak("Sekhmet: ulid_time expects 16-byte binary or 26-char string");
417             }
418              
419 0           RETVAL = sekhmet_ulid_time(bin);
420             }
421             OUTPUT:
422             RETVAL
423              
424             SV *
425             ulid_time_ms(input)
426             SV *input
427             CODE:
428             {
429             unsigned char bin[16];
430             STRLEN in_len;
431 0           const char *in_str = SvPV(input, in_len);
432              
433 0 0         if (in_len == 16) {
434 0           memcpy(bin, in_str, 16);
435 0 0         } else if (in_len == 26) {
436 0 0         if (!sekhmet_ulid_decode(bin, in_str, 26))
437 0           croak("Sekhmet: invalid ULID string");
438             } else {
439 0           croak("Sekhmet: ulid_time_ms expects 16-byte binary or 26-char string");
440             }
441              
442             {
443 0           uint64_t ms = sekhmet_ulid_time_ms(bin);
444 0 0         if (ms > (uint64_t)IV_MAX)
445 0           RETVAL = newSVnv((NV)ms);
446             else
447 0           RETVAL = newSViv((IV)ms);
448             }
449             }
450             OUTPUT:
451             RETVAL
452              
453             SV *
454             ulid_to_uuid(input)
455             SV *input
456             CODE:
457             {
458             unsigned char bin[16];
459             char uuid_str[37];
460             STRLEN in_len;
461 0           const char *in_str = SvPV(input, in_len);
462              
463 0 0         if (in_len == 16) {
464 0           memcpy(bin, in_str, 16);
465 0 0         } else if (in_len == 26) {
466 0 0         if (!sekhmet_ulid_decode(bin, in_str, 26))
467 0           croak("Sekhmet: invalid ULID string");
468             } else {
469 0           croak("Sekhmet: ulid_to_uuid expects 16-byte binary or 26-char string");
470             }
471              
472 0           sekhmet_ulid_to_uuid_str(uuid_str, bin);
473 0           uuid_str[36] = '\0';
474 0           RETVAL = newSVpvn(uuid_str, 36);
475             }
476             OUTPUT:
477             RETVAL
478              
479             SV *
480             uuid_to_ulid(input)
481             SV *input
482             CODE:
483             {
484             unsigned char bin[16];
485             char str[27];
486             STRLEN in_len;
487 0           const char *in_str = SvPV(input, in_len);
488              
489 0 0         if (!sekhmet_uuid_to_ulid_bin(bin, in_str, (int)in_len))
490 0           croak("Sekhmet: cannot parse UUID string");
491              
492 0           sekhmet_ulid_encode(str, bin);
493 0           str[26] = '\0';
494 0           RETVAL = newSVpvn(str, 26);
495             }
496             OUTPUT:
497             RETVAL
498              
499             int
500             ulid_compare(input_a, input_b)
501             SV *input_a
502             SV *input_b
503             CODE:
504             {
505             unsigned char a[16], b[16];
506             STRLEN a_len, b_len;
507 0           const char *a_str = SvPV(input_a, a_len);
508 0           const char *b_str = SvPV(input_b, b_len);
509             int cmp;
510              
511 0 0         if (a_len == 16) memcpy(a, a_str, 16);
512 0 0         else if (a_len == 26) {
513 0 0         if (!sekhmet_ulid_decode(a, a_str, 26))
514 0           croak("Sekhmet: invalid first ULID");
515 0           } else croak("Sekhmet: ulid_compare expects 16-byte binary or 26-char string");
516              
517 0 0         if (b_len == 16) memcpy(b, b_str, 16);
518 0 0         else if (b_len == 26) {
519 0 0         if (!sekhmet_ulid_decode(b, b_str, 26))
520 0           croak("Sekhmet: invalid second ULID");
521 0           } else croak("Sekhmet: ulid_compare expects 16-byte binary or 26-char string");
522              
523 0           cmp = sekhmet_ulid_compare(a, b);
524 0 0         RETVAL = (cmp < 0) ? -1 : (cmp > 0) ? 1 : 0;
    0          
525             }
526             OUTPUT:
527             RETVAL
528              
529             int
530             ulid_validate(input)
531             SV *input
532             CODE:
533             {
534             STRLEN in_len;
535 0           const char *in_str = SvPV(input, in_len);
536 0           RETVAL = sekhmet_ulid_validate(in_str, (int)in_len);
537             }
538             OUTPUT:
539             RETVAL