File Coverage

JSON.xs
Criterion Covered Total %
statement 254 285 89.1
branch 179 276 64.8
condition n/a
subroutine n/a
pod n/a
total 433 561 77.1


line stmt bran cond sub pod time code
1             /*
2             * JSON.xs - File::Raw::JSON XS surface
3             *
4             * Two FilePlugins (json, jsonl) registered at BOOT against File::Raw's
5             * plugin API (see ). All four phases wired:
6             * READ - bytes -> Perl structure
7             * WRITE - Perl structure -> bytes
8             * STREAM - chunked feed for jsonl (json plugin rejects with helpful msg)
9             * RECORD - not implemented (record-derived ops route through READ)
10             */
11              
12             #define PERL_NO_GET_CONTEXT
13             #include "EXTERN.h"
14             #include "perl.h"
15             #include "XSUB.h"
16              
17             #include "file_plugin.h"
18             #include "file_raw_json.h"
19              
20             #include
21             #include
22              
23             /* ============================================================
24             * Boolean singletons + default stash (declared extern in frj.h so the
25             * codec can hand them out from the parse hot path). Allocated in
26             * BOOT and marked read-only - users mutating the inner SV would
27             * corrupt every other reference, so we make the OS catch the mistake
28             * at write time. */
29              
30             SV *g_frj_true_sv = NULL;
31             SV *g_frj_false_sv = NULL;
32             HV *g_frj_default_stash = NULL;
33              
34             static const char *g_boolean_class_name = "File::Raw::JSON::Boolean";
35              
36             static void
37 29           init_boolean_singletons(pTHX)
38             {
39             SV *t_inner, *f_inner;
40 29 50         if (g_frj_true_sv) return;
41              
42 29           g_frj_default_stash = gv_stashpv(g_boolean_class_name, GV_ADD);
43              
44             /* Singletons are NOT marked SvREADONLY: Perl's overload machinery
45             * sets magic on the blessed RV the first time `use overload` is
46             * processed against the class, and that magic write fails on a
47             * read-only RV with "Modification of a read-only value". The
48             * inner scalar could be read-only without breaking overload setup
49             * but in practice overload-blessing also touches the inner state,
50             * so we just leave both writable. The convention "don't mutate
51             * the singleton" is documented; users who do are on their own. */
52 29           t_inner = newSViv(1);
53 29           f_inner = newSViv(0);
54 29           g_frj_true_sv = sv_bless(newRV_noinc(t_inner), g_frj_default_stash);
55 29           g_frj_false_sv = sv_bless(newRV_noinc(f_inner), g_frj_default_stash);
56             }
57              
58             static HV *
59 197           get_boolean_stash(pTHX)
60             {
61 197 50         if (!g_frj_default_stash) init_boolean_singletons(aTHX);
62 197           return g_frj_default_stash;
63             }
64              
65             static HV *
66 198           resolve_boolean_stash(pTHX_ const char *class_name)
67             {
68 198 100         if (!class_name) return get_boolean_stash(aTHX);
69 1           return gv_stashpv(class_name, GV_ADD);
70             }
71              
72             /* Alias an existing File::Raw::JSON CV into the caller's package
73             * under the same short name. Mirrors File::Raw's selective-import
74             * recipe (file.c install_import_entry): create a fresh CV in the
75             * destination glob whose XSUB pointer matches the source. */
76             static void
77 23           fjson_install_alias(pTHX_ const char *pkg, const char *name)
78             {
79             char src_full[256];
80             char dst_full[256];
81             CV *src;
82 23           snprintf(src_full, sizeof(src_full), "File::Raw::JSON::%s", name);
83 23           snprintf(dst_full, sizeof(dst_full), "%s::%s", pkg, name);
84 23           src = get_cv(src_full, 0);
85 23 50         if (!src) {
86 0           warn("File::Raw::JSON: source CV '%s' not found", src_full);
87 0           return;
88             }
89             /* newXS overwrites the destination CV if one already exists. */
90 23           newXS(dst_full, CvXSUB(src), __FILE__);
91             }
92              
93             /* Build a transient options HV from a trailing key/value pair list.
94             * Returns a mortal HV* or NULL if there are no options. Croaks on
95             * odd-count input. Used by file_json_decode / file_json_encode -
96             * lets us reuse decode_opts() unchanged. ax/items mirror dXSARGS;
97             * first_idx is the position of the first key on the stack. */
98             static HV *
99 206           build_opts_hv(pTHX_ I32 ax, I32 items, I32 first_idx, const char *fn)
100             {
101             HV *opts;
102             I32 i;
103 206 100         if (first_idx >= items) return NULL;
104 72 100         if ((items - first_idx) % 2 != 0)
105 2           croak("%s: odd number of options", fn);
106 70           opts = newHV();
107 70           sv_2mortal((SV *)opts);
108 149 100         for (i = first_idx; i + 1 < items; i += 2) {
109             STRLEN klen;
110 79           const char *key = SvPV(ST(i), klen);
111 79           SV *vcopy = newSVsv(ST(i + 1));
112 79 50         if (!hv_store(opts, key, klen, vcopy, 0))
113 0           SvREFCNT_dec(vcopy);
114             }
115 70           return opts;
116             }
117              
118             /* ============================================================
119             * Option decoding
120             *
121             * Maps a Perl HV onto json_options_t. Unknown keys croak (catches
122             * typos like 'pretty_print'). The boolean_class option is returned
123             * separately because it lives outside the struct. */
124              
125             static const char *VALID_OPT_KEYS[] = {
126             "mode", "pretty", "indent", "sort_keys", "canonical",
127             "utf8", "relaxed", "allow_nonref", "allow_nan_inf",
128             "ordered",
129             "max_depth", "eol", "boolean_class",
130             "plugin", /* present in HV file_plugin_dispatch_* builds */
131             NULL
132             };
133              
134             static int
135 279           known_opt(const char *key, STRLEN klen)
136             {
137             const char *const *p;
138 2745 100         for (p = VALID_OPT_KEYS; *p; p++) {
139 2743 100         if (strlen(*p) == klen && memcmp(*p, key, klen) == 0) return 1;
    100          
140             }
141 2           return 0;
142             }
143              
144             static const char *
145 350           decode_opts(pTHX_ HV *opts_hv, json_options_t *o)
146             {
147 350           const char *boolean_class = NULL;
148             HE *he;
149              
150 350 100         if (!opts_hv) return NULL;
151              
152 216           hv_iterinit(opts_hv);
153 490 100         while ((he = hv_iternext(opts_hv))) {
154             I32 klen_i;
155 279           const char *key = hv_iterkey(he, &klen_i);
156 279           STRLEN klen = (STRLEN)klen_i;
157 279           SV *val = hv_iterval(opts_hv, he);
158              
159 279 100         if (!known_opt(key, klen)) {
160 2           croak("File::Raw::JSON: unknown option '%.*s'",
161             (int)klen, key);
162             }
163 277 50         if (!SvOK(val)) continue;
164              
165 289 100         if (klen == 4 && memcmp(key, "mode", 4) == 0) {
    50          
166             STRLEN mlen;
167 13           const char *mpv = SvPV(val, mlen);
168 13 100         if (mlen == 8 && memcmp(mpv, "document", 8) == 0)
    50          
169 0           o->mode = JSON_MODE_DOCUMENT;
170 13 100         else if (mlen == 5 && memcmp(mpv, "lines", 5) == 0)
    50          
171 12           o->mode = JSON_MODE_LINES;
172             else
173 1           croak("File::Raw::JSON: mode must be 'document' or 'lines' "
174             "(got '%.*s')", (int)mlen, mpv);
175             }
176 264 100         else if (klen == 6 && memcmp(key, "pretty", 6) == 0)
    100          
177 16           o->pretty = SvTRUE(val) ? 1 : 0;
178 248 100         else if (klen == 6 && memcmp(key, "indent", 6) == 0) {
    100          
179 4           IV n = SvIV(val);
180 4 100         if (n != 2 && n != 4)
    100          
181 2           croak("File::Raw::JSON: indent must be 2 or 4 (got %ld); "
182             "arbitrary indent strings planned for v0.02",
183             (long)n);
184 2           o->indent = (int)n;
185             }
186 244 100         else if (klen == 9 && memcmp(key, "sort_keys", 9) == 0)
    100          
187 55           o->sort_keys = SvTRUE(val) ? 1 : 0;
188 189 100         else if (klen == 9 && memcmp(key, "canonical", 9) == 0)
    100          
189 3           o->canonical = SvTRUE(val) ? 1 : 0;
190 186 50         else if (klen == 4 && memcmp(key, "utf8", 4) == 0)
    0          
191 0           o->utf8 = SvTRUE(val) ? 1 : 0;
192 186 100         else if (klen == 7 && memcmp(key, "relaxed", 7) == 0)
    100          
193 5           o->relaxed = SvTRUE(val) ? 1 : 0;
194 181 100         else if (klen == 12 && memcmp(key, "allow_nonref", 12) == 0)
    50          
195 1           o->allow_nonref = SvTRUE(val) ? 1 : 0;
196 180 100         else if (klen == 13 && memcmp(key, "allow_nan_inf", 13) == 0)
    100          
197 2           o->allow_nan_inf = SvTRUE(val) ? 1 : 0;
198 178 100         else if (klen == 7 && memcmp(key, "ordered", 7) == 0)
    50          
199 31           o->ordered = SvTRUE(val) ? 1 : 0;
200 147 100         else if (klen == 9 && memcmp(key, "max_depth", 9) == 0) {
    50          
201 1           IV n = SvIV(val);
202 1 50         if (n < 1) croak("File::Raw::JSON: max_depth must be >= 1");
203 1           o->max_depth = (int)n;
204             }
205 146 100         else if (klen == 3 && memcmp(key, "eol", 3) == 0) {
    50          
206             STRLEN elen;
207 1           const char *epv = SvPV(val, elen);
208 1 50         if (elen == 0 || elen > 3)
    50          
209 0           croak("File::Raw::JSON: eol must be 1-3 bytes "
210             "(got %lu)", (unsigned long)elen);
211 1           memcpy(o->eol, epv, elen);
212 1           o->eol[elen] = '\0';
213 1           o->eol_len = (int)elen;
214             }
215 145 100         else if (klen == 13 && memcmp(key, "boolean_class", 13) == 0) {
    50          
216             STRLEN clen;
217 1           boolean_class = SvPV(val, clen);
218             (void)clen;
219             }
220             /* "plugin" is the dispatch key; ignore here. */
221             }
222 211           return boolean_class;
223             }
224              
225             /* ============================================================
226             * Plugin phase functions
227             * ============================================================ */
228              
229             /* Per-plugin state pointers (so the plugin descriptor can carry mode). */
230             static json_mode_t MODE_DOCUMENT_TAG = JSON_MODE_DOCUMENT;
231             static json_mode_t MODE_LINES_TAG = JSON_MODE_LINES;
232              
233             static SV *
234 79           json_read(pTHX_ FilePluginContext *ctx)
235             {
236             json_options_t o;
237             const char *boolean_class;
238             HV *bool_stash;
239             STRLEN len;
240             const char *pv;
241              
242 79           json_options_defaults(&o);
243 79 50         if (ctx->plugin_state)
244 79           o.mode = *(const json_mode_t *)ctx->plugin_state;
245              
246 79           boolean_class = decode_opts(aTHX_ ctx->options, &o);
247 77           bool_stash = resolve_boolean_stash(aTHX_ boolean_class);
248              
249 77 50         if (!ctx->data) return &PL_sv_undef;
250 77           pv = SvPV(ctx->data, len);
251              
252 77 100         if (o.mode == JSON_MODE_LINES) {
253 28           AV *av = json_decode_lines(aTHX_ pv, len, &o, bool_stash);
254 27           return newRV_noinc((SV *)av);
255             }
256 49           return json_decode_document(aTHX_ pv, len, &o, bool_stash);
257             }
258              
259             static SV *
260 51           json_write(pTHX_ FilePluginContext *ctx)
261             {
262             json_options_t o;
263 51           json_options_defaults(&o);
264 51 50         if (ctx->plugin_state)
265 51           o.mode = *(const json_mode_t *)ctx->plugin_state;
266 51           (void)decode_opts(aTHX_ ctx->options, &o);
267              
268 49 100         if (o.mode == JSON_MODE_LINES) {
269 11           return json_encode_lines(aTHX_ ctx->data, &o);
270             }
271 38           return json_encode_document(aTHX_ ctx->data, &o);
272             }
273              
274             /* The 'json' plugin rejects STREAM with a helpful redirect. */
275             static int
276 1           json_stream_reject(pTHX_ FilePluginContext *ctx,
277             const char *chunk, size_t len, int eof)
278             {
279             PERL_UNUSED_ARG(chunk);
280             PERL_UNUSED_ARG(len);
281             PERL_UNUSED_ARG(eof);
282 1           ctx->cancel = 1;
283 1           croak("File::Raw::JSON: the 'json' plugin does not support streaming; "
284             "use 'jsonl' for concatenated JSON values, or slurp the whole "
285             "document via File::Raw::slurp(...)");
286             return 1;
287             }
288              
289             /* ============================================================
290             * jsonl streaming
291             *
292             * Buffers bytes across chunks; brace-balancer slices off complete
293             * top-level values; each is parsed by yyjson and emitted via
294             * call_sv(ctx->callback, ...). Mirrors File::Raw::Separated's
295             * sep_stream pattern. State lives in ctx->call_state. */
296              
297             typedef struct {
298             char *acc_buf;
299             STRLEN acc_len;
300             STRLEN acc_cap;
301             json_options_t opts;
302             HV *bool_stash;
303             SV *die_msg; /* propagation slot */
304             } jsonl_stream_state_t;
305              
306             static void
307 16           jsonl_stream_state_free(pTHX_ jsonl_stream_state_t *s)
308             {
309 16 50         if (!s) return;
310 16 50         if (s->acc_buf) Safefree(s->acc_buf);
311 16 100         if (s->die_msg) SvREFCNT_dec(s->die_msg);
312 16           Safefree(s);
313             }
314              
315             static void
316 20           jsonl_acc_append(pTHX_ jsonl_stream_state_t *s, const char *p, STRLEN n)
317             {
318 20 100         if (s->acc_len + n > s->acc_cap) {
319 4 50         STRLEN newcap = s->acc_cap ? s->acc_cap : 8192;
320 12 100         while (newcap < s->acc_len + n) newcap *= 2;
321 4           Renew(s->acc_buf, newcap, char);
322 4           s->acc_cap = newcap;
323             }
324 20 50         if (n) memcpy(s->acc_buf + s->acc_len, p, n);
325 20           s->acc_len += n;
326 20           }
327              
328             static int
329 6041           jsonl_emit_one(pTHX_ FilePluginContext *ctx, jsonl_stream_state_t *s,
330             const char *vp, STRLEN vlen)
331             {
332             yyjson_read_err err;
333             yyjson_doc *doc;
334             SV *value;
335 6041           int count, rc = 0;
336             SV *errsv;
337              
338 6041           doc = yyjson_read_opts((char *)vp, (size_t)vlen,
339             0, NULL, &err);
340 6041 50         if (!doc) {
341             char ctx_buf[64];
342 0           STRLEN copy_len = vlen < sizeof(ctx_buf) - 1
343             ? vlen : sizeof(ctx_buf) - 1;
344 0           memcpy(ctx_buf, vp, copy_len);
345 0           ctx_buf[copy_len] = '\0';
346 0           ctx->cancel = 1;
347 0 0         croak("File::Raw::JSON: stream parse error: %s near \"%s\"",
348             err.msg ? err.msg : "unknown", ctx_buf);
349             }
350 12082 50         value = json_sv_from_yyjson(aTHX_ yyjson_doc_get_root(doc),
351             s->bool_stash, s->opts.ordered,
352             s->opts.max_depth);
353             yyjson_doc_free(doc);
354              
355             {
356 6041           dSP;
357 6041           ENTER; SAVETMPS;
358 6041 50         PUSHMARK(SP);
359 6041 50         XPUSHs(sv_2mortal(value));
360 6041           PUTBACK;
361 6041           count = call_sv(ctx->callback, G_DISCARD | G_EVAL);
362 6041           SPAGAIN;
363             PERL_UNUSED_VAR(count);
364 6041 50         errsv = ERRSV;
365 6041 100         if (SvTRUE(errsv)) {
366 1           s->die_msg = newSVsv(errsv);
367 1           rc = 1;
368             }
369 6041 50         FREETMPS; LEAVE;
370             }
371 6041           return rc;
372             }
373              
374             static int
375 35           jsonl_drain_buffer(pTHX_ FilePluginContext *ctx, jsonl_stream_state_t *s,
376             int allow_truncate)
377             {
378 35           STRLEN cursor = 0;
379 6075 100         while (cursor < s->acc_len) {
380             STRLEN start, end, np;
381 6045           jsonl_scan_t rc = json_jsonl_next(s->acc_buf + cursor,
382 6045           s->acc_len - cursor,
383             &start, &end, &np);
384 6045 100         if (rc == JSONL_FOUND) {
385 6041 100         if (jsonl_emit_one(aTHX_ ctx, s,
386 6041           s->acc_buf + cursor + start, end - start)) {
387 5           return 1; /* user die */
388             }
389 6040           cursor += np;
390 6040           continue;
391             }
392 4 50         if (rc == JSONL_NEED_MORE) {
393             /* Shift the unconsumed prefix to the front and bail until
394             * the next chunk arrives. The "junk before the value" case
395             * (i.e. start > 0 with NEED_MORE) means leading whitespace
396             * + truncated value; just shift everything from cursor. */
397 4 50         if (cursor > 0) {
398 4           memmove(s->acc_buf, s->acc_buf + cursor,
399 4           s->acc_len - cursor);
400 4           s->acc_len -= cursor;
401             }
402 4 50         if (!allow_truncate) return 0;
403             /* eof + truncated value: croak. */
404 0           ctx->cancel = 1;
405 0           croak("File::Raw::JSON: truncated trailing JSON value at end "
406             "of stream");
407             }
408             /* JSONL_NO_OPENER: only whitespace remains, or junk byte. */
409 0           cursor += start;
410 0 0         if (cursor >= s->acc_len) break; /* all whitespace */
411 0 0         if (s->opts.relaxed) { cursor++; continue; }
412 0           ctx->cancel = 1;
413 0           croak("File::Raw::JSON: unexpected byte at offset %lu in stream "
414             "(expected '{' or '[' to start a JSONL value)",
415             (unsigned long)cursor);
416             }
417             /* Drop the consumed prefix so the buffer doesn't grow without
418             * bound across chunks. */
419 30 100         if (cursor > 0 && cursor < s->acc_len) {
    50          
420 0           memmove(s->acc_buf, s->acc_buf + cursor, s->acc_len - cursor);
421 0           s->acc_len -= cursor;
422 30 50         } else if (cursor >= s->acc_len) {
423 30           s->acc_len = 0;
424             }
425 30           return 0;
426             }
427              
428             static int
429 35           jsonl_stream(pTHX_ FilePluginContext *ctx,
430             const char *chunk, size_t len, int eof)
431             {
432 35           jsonl_stream_state_t *s = (jsonl_stream_state_t *)ctx->call_state;
433              
434 35 100         if (!s) {
435             const char *boolean_class;
436 16           Newxz(s, 1, jsonl_stream_state_t);
437 16           json_options_defaults(&s->opts);
438 16 50         if (ctx->plugin_state)
439 16           s->opts.mode = *(const json_mode_t *)ctx->plugin_state;
440 16           boolean_class = decode_opts(aTHX_ ctx->options, &s->opts);
441 16           s->bool_stash = resolve_boolean_stash(aTHX_ boolean_class);
442 16           s->acc_cap = 8192;
443 16           Newx(s->acc_buf, s->acc_cap, char);
444 16           s->acc_len = 0;
445 16           ctx->call_state = s;
446             }
447              
448 35 100         if (chunk && len > 0) {
    50          
449 20           jsonl_acc_append(aTHX_ s, chunk, len);
450             }
451              
452 35 100         if (jsonl_drain_buffer(aTHX_ ctx, s, eof ? 1 : 0)) {
453 1           SV *die_msg = s->die_msg;
454             STRLEN dlen;
455 1 50         SV *m = die_msg ? newSVsv(die_msg) : NULL;
456 1           jsonl_stream_state_free(aTHX_ s);
457 1           ctx->call_state = NULL;
458 1           ctx->cancel = 1;
459 1 50         if (m) {
460 1           const char *dpv = SvPV(m, dlen);
461 1           sv_2mortal(m);
462 1           croak("%.*s", (int)dlen, dpv);
463             }
464 0           croak("File::Raw::JSON: stream cancelled");
465             }
466              
467 34 100         if (eof) {
468 15           jsonl_stream_state_free(aTHX_ s);
469 15           ctx->call_state = NULL;
470             }
471 34           return 0;
472             }
473              
474             /* ============================================================
475             * Plugin descriptors. Statics so the registry's non-owning pointer
476             * stays valid for the life of the process.
477             * ============================================================ */
478              
479             static FilePlugin json_plugin;
480             static FilePlugin jsonl_plugin;
481              
482             /* ============================================================ */
483              
484             MODULE = File::Raw::JSON PACKAGE = File::Raw::JSON
485              
486             PROTOTYPES: DISABLE
487              
488             # ---- direct in-memory codec entry points ------------------------
489             #
490             # file_json_decode($bytes, ?key => value, ...) -> parsed value
491             # file_json_encode($value, ?key => value, ...) -> JSON bytes
492             #
493             # These bypass File::Raw's plugin pipeline entirely - no path, no
494             # syscalls, just bytes <-> Perl structure. Same options grammar as
495             # the plugin tail (mode, pretty, indent, sort_keys, canonical,
496             # ordered, relaxed, allow_nonref, allow_nan_inf, max_depth, eol,
497             # boolean_class, utf8); odd-count tails croak. See build_opts_hv
498             # above the first MODULE block for the option-collection helper.
499              
500             # File::Raw::JSON->import(...) - selective installer. Mirrors
501             # File::Raw's pattern (file.c XS_file_import). `use File::Raw::JSON`
502             # with no arg list = no-op; with a list of names, each requested
503             # function CV is aliased into the caller's package via newXS, sharing
504             # the underlying XSUB pointer with the source CV.
505             #
506             # Recognised: file_json_decode, file_json_encode, :codec (= both),
507             # :all (= same). Unknown names warn but don't die, matching File::Raw.
508             void
509             import(...)
510             PREINIT:
511             const char *pkg;
512             I32 i;
513             PPCODE:
514 37 50         pkg = CopSTASHPV(PL_curcop);
    50          
    50          
    50          
    0          
    50          
    50          
515 37 100         if (items <= 1) XSRETURN_EMPTY;
516              
517 36 100         for (i = 1; i < items; i++) {
518             STRLEN len;
519 22           const char *arg = SvPV(ST(i), len);
520              
521 22 100         if ((len == 6 && strEQ(arg, ":codec")) ||
    50          
522 20 100         (len == 4 && strEQ(arg, ":all")))
    100          
523             {
524 3           fjson_install_alias(aTHX_ pkg, "file_json_decode");
525 3           fjson_install_alias(aTHX_ pkg, "file_json_encode");
526 20           continue;
527             }
528 19 100         if ((len == 16 && strEQ(arg, "file_json_decode")) ||
    100          
529 11 100         (len == 16 && strEQ(arg, "file_json_encode")))
    50          
530             {
531 17           fjson_install_alias(aTHX_ pkg, arg);
532 17           continue;
533             }
534 2           warn("File::Raw::JSON: '%.*s' is not exported", (int)len, arg);
535             }
536 14           XSRETURN_EMPTY;
537              
538             SV *
539             file_json_decode(bytes, ...)
540             SV *bytes
541             PREINIT:
542             json_options_t o;
543             HV *opts_hv;
544             const char *boolean_class;
545             HV *bool_stash;
546             STRLEN len;
547             const char *pv;
548             CODE:
549 107           json_options_defaults(&o);
550 107           o.mode = JSON_MODE_DOCUMENT;
551 107           opts_hv = build_opts_hv(aTHX_ ax, items, 1, "file_json_decode");
552 106           boolean_class = decode_opts(aTHX_ opts_hv, &o);
553 105           bool_stash = resolve_boolean_stash(aTHX_ boolean_class);
554              
555 105 50         if (!bytes || !SvOK(bytes)) XSRETURN_UNDEF;
    100          
556 104           pv = SvPV(bytes, len);
557              
558 104 100         if (o.mode == JSON_MODE_LINES) {
559 10           AV *av = json_decode_lines(aTHX_ pv, len, &o, bool_stash);
560 10           RETVAL = newRV_noinc((SV *)av);
561             } else {
562 94           SV *out = json_decode_document(aTHX_ pv, len, &o, bool_stash);
563 92 50         RETVAL = out ? out : &PL_sv_undef;
564 92 50         if (RETVAL == &PL_sv_undef) SvREFCNT_inc(RETVAL);
565             }
566             OUTPUT:
567             RETVAL
568              
569             SV *
570             file_json_encode(value, ...)
571             SV *value
572             PREINIT:
573             json_options_t o;
574             HV *opts_hv;
575             CODE:
576 99           json_options_defaults(&o);
577 99           o.mode = JSON_MODE_DOCUMENT;
578 99           opts_hv = build_opts_hv(aTHX_ ax, items, 1, "file_json_encode");
579 98           (void)decode_opts(aTHX_ opts_hv, &o);
580              
581 98 100         if (o.mode == JSON_MODE_LINES) {
582 2           RETVAL = json_encode_lines(aTHX_ value, &o);
583             } else {
584 96           RETVAL = json_encode_document(aTHX_ value, &o);
585             }
586 93 50         if (!RETVAL) {
587 0           RETVAL = &PL_sv_undef;
588 0           SvREFCNT_inc(RETVAL);
589             }
590             OUTPUT:
591             RETVAL
592              
593             BOOT:
594             {
595 29           init_boolean_singletons(aTHX);
596              
597 29           json_plugin.name = "json";
598 29           json_plugin.read_fn = json_read;
599 29           json_plugin.write_fn = json_write;
600 29           json_plugin.record_fn = NULL;
601 29           json_plugin.stream_fn = json_stream_reject;
602 29           json_plugin.state = &MODE_DOCUMENT_TAG;
603 29 50         if (file_register_plugin(aTHX_ &json_plugin) <= 0)
604 0           warn("File::Raw::JSON: failed to register 'json' plugin");
605              
606 29           jsonl_plugin.name = "jsonl";
607 29           jsonl_plugin.read_fn = json_read; /* mode tag selects MODE_LINES */
608 29           jsonl_plugin.write_fn = json_write;
609 29           jsonl_plugin.record_fn = NULL;
610 29           jsonl_plugin.stream_fn = jsonl_stream;
611 29           jsonl_plugin.state = &MODE_LINES_TAG;
612 29 50         if (file_register_plugin(aTHX_ &jsonl_plugin) <= 0)
613 0           warn("File::Raw::JSON: failed to register 'jsonl' plugin");
614             }
615              
616              
617             # ============================================================
618             # File::Raw::JSON::Boolean - XSUB constructors + overload bodies
619             # ============================================================
620             #
621             # All four overload entry points (bool / numify / stringify / not)
622             # run as XSUBs rather than Perl subs, ~3x faster than the pure-Perl
623             # overload that was here before. The overload table itself is still
624             # wired up by `use overload ...` in Boolean.pm - that's the cheapest
625             # way to register, and the dispatch cost is identical regardless of
626             # whether the body is a Perl sub or an XSUB.
627             #
628             # Calling convention: overload invokes our handlers with three args
629             # (self, other, swap). We only need self; ignore the rest. Returning
630             # the static PL_sv_yes / PL_sv_no avoids per-call SV allocation.
631              
632             MODULE = File::Raw::JSON PACKAGE = File::Raw::JSON::Boolean
633              
634             PROTOTYPES: DISABLE
635              
636             void
637             TRUE(...)
638             PPCODE:
639             PERL_UNUSED_VAR(items);
640 6 50         if (!g_frj_true_sv) init_boolean_singletons(aTHX);
641 6 50         SvREFCNT_inc_simple_void(g_frj_true_sv);
642 6 50         XPUSHs(sv_2mortal(g_frj_true_sv));
643 6           XSRETURN(1);
644              
645             void
646             FALSE(...)
647             PPCODE:
648             PERL_UNUSED_VAR(items);
649 4 50         if (!g_frj_false_sv) init_boolean_singletons(aTHX);
650 4 50         SvREFCNT_inc_simple_void(g_frj_false_sv);
651 4 50         XPUSHs(sv_2mortal(g_frj_false_sv));
652 4           XSRETURN(1);
653              
654             SV *
655             overload_bool(self, other, swap)
656             SV *self
657             SV *other
658             SV *swap
659             OVERLOAD: bool
660             CODE:
661             PERL_UNUSED_VAR(other);
662             PERL_UNUSED_VAR(swap);
663 7 50         RETVAL = (SvROK(self) && SvTRUE(SvRV(self)))
664 14 50         ? &PL_sv_yes : &PL_sv_no;
665 7 50         SvREFCNT_inc_simple_void(RETVAL); /* OUTPUT typemap will mortalise */
666             OUTPUT:
667             RETVAL
668              
669             SV *
670             overload_numify(self, other, swap)
671             SV *self
672             SV *other
673             SV *swap
674             OVERLOAD: 0+
675             CODE:
676             PERL_UNUSED_VAR(other);
677             PERL_UNUSED_VAR(swap);
678 0 0         RETVAL = newSViv(
    0          
679             (SvROK(self) && SvTRUE(SvRV(self))) ? 1 : 0
680             );
681             OUTPUT:
682             RETVAL
683              
684             SV *
685             overload_stringify(self, other, swap)
686             SV *self
687             SV *other
688             SV *swap
689             OVERLOAD: \"\"
690             CODE:
691             PERL_UNUSED_VAR(other);
692             PERL_UNUSED_VAR(swap);
693 0 0         RETVAL = newSVpvn(
    0          
694             (SvROK(self) && SvTRUE(SvRV(self))) ? "1" : "0", 1
695             );
696             OUTPUT:
697             RETVAL
698              
699             SV *
700             overload_not(self, other, swap)
701             SV *self
702             SV *other
703             SV *swap
704             OVERLOAD: !
705             CODE:
706             PERL_UNUSED_VAR(other);
707             PERL_UNUSED_VAR(swap);
708 10 50         if (SvROK(self) && SvTRUE(SvRV(self)))
    100          
709 2           RETVAL = newSVpvn("", 0);
710             else
711 8           RETVAL = newSViv(1);
712             OUTPUT:
713             RETVAL
714              
715             int
716             is_true(self)
717             SV *self
718             CODE:
719 0 0         RETVAL = (SvROK(self) && sv_isa(self, "File::Raw::JSON::Boolean")
    0          
720 0 0         && SvTRUE(SvRV(self))) ? 1 : 0;
    0          
721             OUTPUT:
722             RETVAL
723              
724             int
725             is_false(self)
726             SV *self
727             CODE:
728 0 0         RETVAL = (SvROK(self) && sv_isa(self, "File::Raw::JSON::Boolean")
    0          
729 0 0         && !SvTRUE(SvRV(self))) ? 1 : 0;
    0          
730             OUTPUT:
731             RETVAL