File Coverage

XS.xs
Criterion Covered Total %
statement 1099 1307 84.0
branch 700 1032 67.8
condition n/a
subroutine n/a
pod n/a
total 1799 2339 76.9


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "XSUB.h"
4              
5             #include
6             #include
7             #include
8             #include
9              
10             typedef struct {
11             const char *text;
12             STRLEN len;
13             STRLEN pos;
14             } brace_parser_t;
15              
16             typedef struct {
17             int pretty;
18             int canonical;
19             int indent;
20             } brace_encode_opts_t;
21              
22 179           static int is_ident_start_char(char c) {
23 179 50         return (c == '_') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
    100          
    50          
    100          
    100          
24             }
25              
26 134           static int is_ident_char(char c) {
27 134 100         return is_ident_start_char(c) || (c >= '0' && c <= '9') || c == '-';
    100          
    50          
    50          
28             }
29              
30 132           static void bp_skip_ws(brace_parser_t *p) {
31 178 100         while (p->pos < p->len) {
32 169           char ch = p->text[p->pos];
33 169 100         if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') {
    50          
    100          
    50          
34 46           p->pos++;
35 46           continue;
36             }
37 123           break;
38             }
39 132           }
40              
41 335           static char bp_peek(brace_parser_t *p) {
42 335 50         if (p->pos >= p->len) {
43 0           return '\0';
44             }
45 335           return p->text[p->pos];
46             }
47              
48 1           static void bp_throw(const char *msg) {
49 1           croak("%s", msg);
50             }
51              
52 64           static void bp_expect(brace_parser_t *p, char expected) {
53 64           char got = bp_peek(p);
54 64 50         if (!got || got != expected) {
    100          
55             char msg[64];
56 1           snprintf(msg, sizeof(msg), "Expected '%c'", expected);
57 1           bp_throw(msg);
58             }
59 63           p->pos++;
60 63           }
61              
62 41           static int bp_consume_literal(brace_parser_t *p, const char *literal) {
63 41           STRLEN litlen = (STRLEN)strlen(literal);
64 41 100         if (p->pos + litlen > p->len) {
65 9           return 0;
66             }
67 32 100         if (strncmp(p->text + p->pos, literal, litlen) != 0) {
68 31           return 0;
69             }
70              
71 1 50         if (p->pos + litlen < p->len) {
72 1           char next = p->text[p->pos + litlen];
73 1 50         if (is_ident_char(next)) {
74 0           return 0;
75             }
76             }
77              
78 1           p->pos += litlen;
79 1           return 1;
80             }
81              
82             static SV *bp_parse_value(brace_parser_t *p);
83              
84 188           static int is_numeric_token(const char *s, STRLEN n) {
85 188           STRLEN i = 0;
86 188           int has_digit = 0;
87              
88 188 50         if (i < n && (s[i] == '+' || s[i] == '-')) {
    50          
    100          
89 1           i++;
90             }
91              
92 256 100         while (i < n && isdigit((unsigned char)s[i])) {
    100          
93 68           has_digit = 1;
94 68           i++;
95             }
96              
97 188 100         if (i < n && s[i] == '.') {
    100          
98 4           i++;
99 13 100         while (i < n && isdigit((unsigned char)s[i])) {
    50          
100 9           has_digit = 1;
101 9           i++;
102             }
103             }
104              
105 188 100         if (!has_digit) {
106 137           return 0;
107             }
108              
109 51 100         if (i < n && (s[i] == 'e' || s[i] == 'E')) {
    100          
    50          
110 1           i++;
111 1 50         if (i < n && (s[i] == '+' || s[i] == '-')) {
    50          
    50          
112 0           i++;
113             }
114 1 50         if (i >= n || !isdigit((unsigned char)s[i])) {
    50          
115 0           return 0;
116             }
117 2 100         while (i < n && isdigit((unsigned char)s[i])) {
    50          
118 1           i++;
119             }
120             }
121              
122 51           return i == n;
123             }
124              
125 10           static SV *bp_parse_number(brace_parser_t *p) {
126 10           STRLEN start = p->pos;
127              
128 10 50         if (bp_peek(p) == '-') {
129 0           p->pos++;
130             }
131              
132 10 50         if (!isdigit((unsigned char)bp_peek(p))) {
133 0           bp_throw("Invalid number");
134             }
135              
136 10 50         if (bp_peek(p) == '0') {
137 0           p->pos++;
138             } else {
139 21 100         while (isdigit((unsigned char)bp_peek(p))) {
140 11           p->pos++;
141             }
142             }
143              
144 10 50         if (bp_peek(p) == '.') {
145 0           p->pos++;
146 0 0         if (!isdigit((unsigned char)bp_peek(p))) {
147 0           bp_throw("Invalid number");
148             }
149 0 0         while (isdigit((unsigned char)bp_peek(p))) {
150 0           p->pos++;
151             }
152             }
153              
154 10 50         if (bp_peek(p) == 'e' || bp_peek(p) == 'E') {
    50          
155 0           p->pos++;
156 0 0         if (bp_peek(p) == '+' || bp_peek(p) == '-') {
    0          
157 0           p->pos++;
158             }
159 0 0         if (!isdigit((unsigned char)bp_peek(p))) {
160 0           bp_throw("Invalid number");
161             }
162 0 0         while (isdigit((unsigned char)bp_peek(p))) {
163 0           p->pos++;
164             }
165             }
166              
167 10           STRLEN n = p->pos - start;
168 10           SV *token = newSVpvn(p->text + start, n);
169 10           const char *s = SvPV_nolen(token);
170              
171 10 50         if (memchr(s, '.', n) || memchr(s, 'e', n) || memchr(s, 'E', n)) {
    50          
    50          
172 0           NV nv = SvNV(token);
173 0           SvREFCNT_dec(token);
174 0           return newSVnv(nv);
175             }
176              
177 10           IV iv = SvIV(token);
178 10           SvREFCNT_dec(token);
179 10           return newSViv(iv);
180             }
181              
182 5           static SV *bp_parse_string(brace_parser_t *p) {
183 5           bp_expect(p, '"');
184              
185 5           SV *out = newSVpvn("", 0);
186              
187 25 50         while (p->pos < p->len) {
188 25           char ch = p->text[p->pos++];
189              
190 25 100         if (ch == '"') {
191 5           return out;
192             }
193              
194 20 50         if (ch == '\\') {
195 0 0         if (p->pos >= p->len) {
196 0           SvREFCNT_dec(out);
197 0           bp_throw("Unexpected end of input in string escape");
198             }
199              
200 0           char esc = p->text[p->pos++];
201 0           switch (esc) {
202 0           case '"': sv_catpvn(out, "\"", 1); break;
203 0           case '\\': sv_catpvn(out, "\\", 1); break;
204 0           case '/': sv_catpvn(out, "/", 1); break;
205 0           case 'n': sv_catpvn(out, "\n", 1); break;
206 0           case 'r': sv_catpvn(out, "\r", 1); break;
207 0           case 't': sv_catpvn(out, "\t", 1); break;
208 0           case 'f': sv_catpvn(out, "\f", 1); break;
209 0           case 'b': {
210 0           char bs = '\b';
211 0           sv_catpvn(out, &bs, 1);
212 0           break;
213             }
214 0           case 'u': {
215 0 0         if (p->pos + 4 > p->len) {
216 0           SvREFCNT_dec(out);
217 0           bp_throw("Invalid unicode escape");
218             }
219             char hexbuf[5];
220 0           memcpy(hexbuf, p->text + p->pos, 4);
221 0           hexbuf[4] = '\0';
222 0           p->pos += 4;
223 0           unsigned long uv = strtoul(hexbuf, NULL, 16);
224 0 0         if (uv <= 0x7F) {
225 0           char c = (char)uv;
226 0           sv_catpvn(out, &c, 1);
227             } else {
228             char tmp[UTF8_MAXBYTES + 1];
229 0           UV len = uvchr_to_utf8((U8*)tmp, (UV)uv) - (U8*)tmp;
230 0           sv_catpvn(out, tmp, (STRLEN)len);
231             }
232 0           break;
233             }
234 0           default:
235 0           SvREFCNT_dec(out);
236 0           bp_throw("Unknown escape sequence");
237             }
238 0           continue;
239             }
240              
241 20           sv_catpvn(out, &ch, 1);
242             }
243              
244 0           SvREFCNT_dec(out);
245 0           bp_throw("Unterminated string");
246 0           return &PL_sv_undef;
247             }
248              
249 13           static SV *bp_parse_key(brace_parser_t *p) {
250 13           char ch = bp_peek(p);
251 13 50         if (ch == '"') {
252 0           return bp_parse_string(p);
253             }
254              
255 13 50         if (!is_ident_start_char(ch)) {
256 0           bp_throw("Expected object key");
257             }
258              
259 13           STRLEN start = p->pos;
260 13           p->pos++;
261 45 100         while (is_ident_char(bp_peek(p))) {
262 32           p->pos++;
263             }
264              
265 13           return newSVpvn(p->text + start, p->pos - start);
266             }
267              
268 17           static int sv_cmp_qsort(const void *a, const void *b) {
269 17           SV *sa = *(SV**)a;
270 17           SV *sb = *(SV**)b;
271             STRLEN na, nb;
272 17           const char *pa = SvPV(sa, na);
273 17           const char *pb = SvPV(sb, nb);
274 17           STRLEN n = na < nb ? na : nb;
275 17           int c = memcmp(pa, pb, n);
276 17 50         if (c != 0) return c;
277 0 0         if (na < nb) return -1;
278 0 0         if (na > nb) return 1;
279 0           return 0;
280             }
281              
282 11           static AV *hv_sorted_keys(HV *hv) {
283 11           I32 count = hv_iterinit(hv);
284 11 50         SV **arr = (SV**)safemalloc(sizeof(SV*) * (count > 0 ? count : 1));
285 11           I32 idx = 0;
286             HE *he;
287              
288 35 100         while ((he = hv_iternext(hv)) != NULL) {
289 24           SV *k = hv_iterkeysv(he);
290 24           arr[idx++] = newSVsv(k);
291             }
292              
293 11 100         if (idx > 1) {
294 7           qsort(arr, (size_t)idx, sizeof(SV*), sv_cmp_qsort);
295             }
296              
297 11           AV *av = newAV();
298 35 100         for (I32 i = 0; i < idx; i++) {
299 24           av_push(av, arr[i]);
300             }
301              
302 11           safefree(arr);
303 11           return av;
304             }
305              
306 17           static int sv_is_identifier_key(SV *key) {
307             STRLEN n;
308 17           const char *s = SvPV(key, n);
309 17 50         if (n == 0 || !is_ident_start_char(s[0])) {
    50          
310 0           return 0;
311             }
312 60 100         for (STRLEN i = 1; i < n; i++) {
313 43 50         if (!is_ident_char(s[i])) {
314 0           return 0;
315             }
316             }
317 17           return 1;
318             }
319              
320 18           static SV *bp_parse_tabular_value(brace_parser_t *p) {
321 18           STRLEN start = p->pos;
322 75 50         while (p->pos < p->len) {
323 75           char c = p->text[p->pos];
324 75 100         if (c == ',' || c == '\n' || c == '\r') {
    100          
    50          
325             break;
326             }
327 57           p->pos++;
328             }
329              
330 18           STRLEN end = p->pos;
331 18 50         while (end > start && (p->text[end - 1] == ' ' || p->text[end - 1] == '\t')) {
    50          
    50          
332 0           end--;
333             }
334              
335 18           STRLEN n = end - start;
336 18 100         if (is_numeric_token(p->text + start, n)) {
337 6           SV *tmp = newSVpvn(p->text + start, n);
338             SV *out;
339 6 50         if (memchr(SvPV_nolen(tmp), '.', n) || memchr(SvPV_nolen(tmp), 'e', n) || memchr(SvPV_nolen(tmp), 'E', n)) {
    50          
    50          
340 0           out = newSVnv(SvNV(tmp));
341             } else {
342 6           out = newSViv(SvIV(tmp));
343             }
344 6           SvREFCNT_dec(tmp);
345 6           return out;
346             }
347              
348 12           return newSVpvn(p->text + start, n);
349             }
350              
351 3           static SV *bp_parse_tabular(brace_parser_t *p) {
352 3           HV *result = newHV();
353              
354 6 50         while (p->pos < p->len) {
355 6           bp_skip_ws(p);
356 6 100         if (p->pos >= p->len) {
357 3           break;
358             }
359              
360 3 50         if (!is_ident_start_char(bp_peek(p))) {
361 0           break;
362             }
363              
364 3           STRLEN key_start = p->pos;
365 3           p->pos++;
366 15 100         while (is_ident_char(bp_peek(p))) {
367 12           p->pos++;
368             }
369 3           SV *key_sv = newSVpvn(p->text + key_start, p->pos - key_start);
370              
371 3           bp_expect(p, '[');
372              
373 3           STRLEN cnt_start = p->pos;
374 6 100         while (isdigit((unsigned char)bp_peek(p))) {
375 3           p->pos++;
376             }
377 3 50         if (p->pos == cnt_start) {
378 0           SvREFCNT_dec(key_sv);
379 0           SvREFCNT_dec((SV*)result);
380 0           bp_throw("Expected count in [...]");
381             }
382              
383 3           SV *cnt_sv = newSVpvn(p->text + cnt_start, p->pos - cnt_start);
384 3           IV count = SvIV(cnt_sv);
385 3           SvREFCNT_dec(cnt_sv);
386              
387 3           bp_expect(p, ']');
388 3           bp_expect(p, '{');
389              
390 3           AV *fields = newAV();
391 6           while (1) {
392 9 50         if (!is_ident_start_char(bp_peek(p))) {
393 0           SvREFCNT_dec(key_sv);
394 0           SvREFCNT_dec((SV*)fields);
395 0           SvREFCNT_dec((SV*)result);
396 0           bp_throw("Expected field name");
397             }
398              
399 9           STRLEN fs = p->pos;
400 9           p->pos++;
401 30 100         while (is_ident_char(bp_peek(p))) {
402 21           p->pos++;
403             }
404 9           av_push(fields, newSVpvn(p->text + fs, p->pos - fs));
405              
406 9 100         if (bp_peek(p) == ',') {
407 6           p->pos++;
408 6           continue;
409             }
410 3           break;
411             }
412              
413 3           bp_expect(p, '}');
414 3           bp_expect(p, ':');
415              
416 3           AV *rows = newAV();
417 3           I32 nfields = av_len(fields) + 1;
418              
419 9 100         for (IV r = 0; r < count; r++) {
420 6 50         while (p->pos < p->len) {
421 6           char c = p->text[p->pos++];
422 6 50         if (c == '\n') {
423 6           break;
424             }
425             }
426              
427 18 50         while (p->pos < p->len) {
428 18           char c = p->text[p->pos];
429 18 100         if (c == ' ' || c == '\t') {
    50          
430 12           p->pos++;
431 12           continue;
432             }
433 6           break;
434             }
435              
436 6           HV *row = newHV();
437 24 100         for (I32 fi = 0; fi < nfields; fi++) {
438 18 100         if (fi > 0) {
439 12           bp_expect(p, ',');
440             }
441              
442 18           SV **fsv = av_fetch(fields, fi, 0);
443 18           SV *v = bp_parse_tabular_value(p);
444             STRLEN kn;
445 18           const char *k = SvPV(*fsv, kn);
446 18           hv_store(row, k, (I32)kn, v, 0);
447             }
448 6           av_push(rows, newRV_noinc((SV*)row));
449             }
450              
451             STRLEN kn;
452 3           const char *k = SvPV(key_sv, kn);
453 3           hv_store(result, k, (I32)kn, newRV_noinc((SV*)rows), 0);
454              
455 3           SvREFCNT_dec(key_sv);
456 3           SvREFCNT_dec((SV*)fields);
457             }
458              
459 3           return newRV_noinc((SV*)result);
460             }
461              
462 4           static SV *bp_parse_array(brace_parser_t *p) {
463 4           bp_expect(p, '[');
464 4           bp_skip_ws(p);
465              
466 4           AV *array = newAV();
467              
468 4 50         if (bp_peek(p) == ']') {
469 0           p->pos++;
470 0           return newRV_noinc((SV*)array);
471             }
472              
473 5           while (1) {
474 9           SV *v = bp_parse_value(p);
475 9           av_push(array, v);
476              
477 9           bp_skip_ws(p);
478 9 100         if (bp_peek(p) == ',') {
479 5           p->pos++;
480 5           bp_skip_ws(p);
481 5           continue;
482             }
483 4           break;
484             }
485              
486 4           bp_skip_ws(p);
487 4           bp_expect(p, ']');
488              
489 3           return newRV_noinc((SV*)array);
490             }
491              
492 6           static SV *bp_parse_object(brace_parser_t *p) {
493 6           bp_expect(p, '{');
494 6           bp_skip_ws(p);
495              
496 6           HV *hash = newHV();
497              
498 6 50         if (bp_peek(p) == '}') {
499 0           p->pos++;
500 0           return newRV_noinc((SV*)hash);
501             }
502              
503 7           while (1) {
504 13           bp_skip_ws(p);
505 13           SV *key_sv = bp_parse_key(p);
506              
507 13           bp_skip_ws(p);
508 13           bp_expect(p, ':');
509 13           bp_skip_ws(p);
510              
511 13           SV *value = bp_parse_value(p);
512              
513             STRLEN kn;
514 12           const char *k = SvPV(key_sv, kn);
515 12           hv_store(hash, k, (I32)kn, value, 0);
516 12           SvREFCNT_dec(key_sv);
517              
518 12           bp_skip_ws(p);
519 12 100         if (bp_peek(p) == ',') {
520 7           p->pos++;
521 7           continue;
522             }
523 5           break;
524             }
525              
526 5           bp_skip_ws(p);
527 5           bp_expect(p, '}');
528              
529 5           return newRV_noinc((SV*)hash);
530             }
531              
532 29           static SV *bp_parse_value(brace_parser_t *p) {
533 29           bp_skip_ws(p);
534              
535 29           char ch = bp_peek(p);
536 29 50         if (!ch) {
537 0           bp_throw("Unexpected end of input");
538             }
539              
540 29 100         if (ch == '{') return bp_parse_object(p);
541 23 100         if (ch == '[') return bp_parse_array(p);
542 19 100         if (ch == '"') return bp_parse_string(p);
543              
544 14 50         if (bp_consume_literal(p, "null")) return newSV(0);
545 14 100         if (bp_consume_literal(p, "true")) return newSViv(1);
546 13 50         if (bp_consume_literal(p, "false")) return newSViv(0);
547              
548 13 50         if (ch == '-' || isdigit((unsigned char)ch)) {
    100          
549 10           return bp_parse_number(p);
550             }
551              
552 3 50         if (is_ident_start_char(ch)) {
553 3           return bp_parse_tabular(p);
554             }
555              
556 0           bp_throw("Unexpected character");
557 0           return newSV(0);
558             }
559              
560 159           static int sv_is_plain_numberish(SV *sv) {
561 159 50         if (!SvOK(sv)) return 0;
562 159 100         if (SvNOK(sv) || SvIOK(sv) || looks_like_number(sv)) return 1;
    100          
    50          
563 87           return 0;
564             }
565              
566 1           static void encode_quoted_string(SV *out, SV *value) {
567             STRLEN n;
568 1           const char *s = SvPV(value, n);
569 1           sv_catpvn(out, "\"", 1);
570 4 100         for (STRLEN i = 0; i < n; i++) {
571 3           char c = s[i];
572 3           switch (c) {
573 0           case '\\': sv_catpvn(out, "\\\\", 2); break;
574 0           case '"': sv_catpvn(out, "\\\"", 2); break;
575 0           case '\n': sv_catpvn(out, "\\n", 2); break;
576 0           case '\r': sv_catpvn(out, "\\r", 2); break;
577 0           case '\t': sv_catpvn(out, "\\t", 2); break;
578 0           case '\f': sv_catpvn(out, "\\f", 2); break;
579 0           case '\b': sv_catpvn(out, "\\b", 2); break;
580 3           default: sv_catpvn(out, &c, 1); break;
581             }
582             }
583 1           sv_catpvn(out, "\"", 1);
584 1           }
585              
586 5           static int is_tabular_encodable(HV *hv) {
587 5           hv_iterinit(hv);
588             HE *he;
589              
590 8 100         while ((he = hv_iternext(hv)) != NULL) {
591 5           SV *key = hv_iterkeysv(he);
592 5 50         if (!sv_is_identifier_key(key)) return 0;
593              
594 5           SV *val = hv_iterval(hv, he);
595 5 100         if (!SvROK(val) || SvTYPE(SvRV(val)) != SVt_PVAV) return 0;
    50          
596              
597 3           AV *arr = (AV*)SvRV(val);
598 3           I32 arr_len = av_len(arr) + 1;
599 3 50         if (arr_len <= 0) return 0;
600              
601 3           SV **first_row_sv = av_fetch(arr, 0, 0);
602 3 50         if (!first_row_sv || !SvROK(*first_row_sv) || SvTYPE(SvRV(*first_row_sv)) != SVt_PVHV) return 0;
    50          
    50          
603 3           HV *first_row = (HV*)SvRV(*first_row_sv);
604              
605 3           AV *fields = hv_sorted_keys(first_row);
606 3           I32 nfields = av_len(fields) + 1;
607              
608 12 100         for (I32 fi = 0; fi < nfields; fi++) {
609 9           SV **f = av_fetch(fields, fi, 0);
610 9 50         if (!f || !sv_is_identifier_key(*f)) {
    50          
611 0           SvREFCNT_dec((SV*)fields);
612 0           return 0;
613             }
614             }
615              
616 9 100         for (I32 ri = 0; ri < arr_len; ri++) {
617 6           SV **row_sv = av_fetch(arr, ri, 0);
618 6 50         if (!row_sv || !SvROK(*row_sv) || SvTYPE(SvRV(*row_sv)) != SVt_PVHV) {
    50          
    50          
619 0           SvREFCNT_dec((SV*)fields);
620 0           return 0;
621             }
622              
623 6           HV *row = (HV*)SvRV(*row_sv);
624 6           I32 row_count = hv_iterinit(row);
625 6 50         if (row_count != nfields) {
626 0           SvREFCNT_dec((SV*)fields);
627 0           return 0;
628             }
629              
630 24 100         for (I32 fi = 0; fi < nfields; fi++) {
631 18           SV **f = av_fetch(fields, fi, 0);
632             STRLEN fn;
633 18           const char *fname = SvPV(*f, fn);
634 18           SV **cell = hv_fetch(row, fname, (I32)fn, 0);
635 18 50         if (!cell || !SvOK(*cell)) {
    50          
636 0           SvREFCNT_dec((SV*)fields);
637 0           return 0;
638             }
639 18 100         if (!sv_is_plain_numberish(*cell)) {
640             STRLEN cn;
641 12           const char *cs = SvPV(*cell, cn);
642 63 100         for (STRLEN ci = 0; ci < cn; ci++) {
643 51 50         if (cs[ci] == ',' || cs[ci] == '\n' || cs[ci] == '\r') {
    50          
    50          
644 0           SvREFCNT_dec((SV*)fields);
645 0           return 0;
646             }
647             }
648             }
649             }
650             }
651              
652 3           SvREFCNT_dec((SV*)fields);
653             }
654              
655 3           return 1;
656             }
657              
658             static void encode_value_brace(SV *out, SV *value, I32 level, brace_encode_opts_t *opts);
659              
660 6           static void append_indent(SV *out, I32 level, I32 indent) {
661 6           I32 n = level * indent;
662 20 100         for (I32 i = 0; i < n; i++) {
663 14           sv_catpvn(out, " ", 1);
664             }
665 6           }
666              
667 18           static void encode_tabular_value(SV *out, SV *value) {
668 18 50         if (!SvOK(value)) {
669 0           return;
670             }
671 18 100         if (sv_is_plain_numberish(value)) {
672 6           SV *num = newSVnv(SvNV(value));
673 6           sv_catpv(out, SvPV_nolen(num));
674 6           SvREFCNT_dec(num);
675 6           return;
676             }
677 12           sv_catpv(out, SvPV_nolen(value));
678             }
679              
680 5           static void encode_hash_brace(SV *out, HV *hv, I32 level, brace_encode_opts_t *opts) {
681 5           I32 key_count = hv_iterinit(hv);
682 5 50         if (key_count == 0) {
683 0           sv_catpvn(out, "{}", 2);
684 0           return;
685             }
686              
687 5 50         if (level == 0 && is_tabular_encodable(hv)) {
    100          
688 3           AV *keys = hv_sorted_keys(hv);
689 3           I32 nkeys = av_len(keys) + 1;
690              
691 6 100         for (I32 ki = 0; ki < nkeys; ki++) {
692 3           SV **ksv = av_fetch(keys, ki, 0);
693             STRLEN kn;
694 3           const char *k = SvPV(*ksv, kn);
695 3           SV **arr_sv = hv_fetch(hv, k, (I32)kn, 0);
696 3           AV *arr = (AV*)SvRV(*arr_sv);
697 3           I32 arr_len = av_len(arr) + 1;
698              
699 3           SV **first_row_sv = av_fetch(arr, 0, 0);
700 3           HV *first_row = (HV*)SvRV(*first_row_sv);
701 3           AV *fields = hv_sorted_keys(first_row);
702 3           I32 nfields = av_len(fields) + 1;
703              
704 3           sv_catpvf(out, "%s[%ld]{", k, (long)arr_len);
705 12 100         for (I32 fi = 0; fi < nfields; fi++) {
706 9 100         if (fi > 0) sv_catpvn(out, ",", 1);
707 9           SV **fsv = av_fetch(fields, fi, 0);
708 9           sv_catpv(out, SvPV_nolen(*fsv));
709             }
710 3           sv_catpvn(out, "}:\n", 3);
711              
712 9 100         for (I32 ri = 0; ri < arr_len; ri++) {
713 6           SV **row_sv = av_fetch(arr, ri, 0);
714 6           HV *row = (HV*)SvRV(*row_sv);
715 6           sv_catpvn(out, " ", 2);
716 24 100         for (I32 fi = 0; fi < nfields; fi++) {
717 18 100         if (fi > 0) sv_catpvn(out, ",", 1);
718 18           SV **fsv = av_fetch(fields, fi, 0);
719             STRLEN fn;
720 18           const char *fname = SvPV(*fsv, fn);
721 18           SV **cell = hv_fetch(row, fname, (I32)fn, 0);
722 18           encode_tabular_value(out, *cell);
723             }
724 6           sv_catpvn(out, "\n", 1);
725             }
726              
727 3           SvREFCNT_dec((SV*)fields);
728             }
729              
730 3           SvREFCNT_dec((SV*)keys);
731 3           return;
732             }
733              
734             AV *keys;
735 2 50         if (opts->canonical) {
736 2           keys = hv_sorted_keys(hv);
737             } else {
738 0           keys = newAV();
739 0           hv_iterinit(hv);
740             HE *he;
741 0 0         while ((he = hv_iternext(hv)) != NULL) {
742 0           av_push(keys, newSVsv(hv_iterkeysv(he)));
743             }
744             }
745              
746 2           I32 nkeys = av_len(keys) + 1;
747              
748 2 100         if (!opts->pretty) {
749 1           sv_catpvn(out, "{", 1);
750 2 100         for (I32 i = 0; i < nkeys; i++) {
751 1 50         if (i > 0) sv_catpvn(out, ", ", 2);
752 1           SV **ksv = av_fetch(keys, i, 0);
753             STRLEN kn;
754 1           const char *k = SvPV(*ksv, kn);
755              
756 1 50         if (sv_is_identifier_key(*ksv)) {
757 1           sv_catpvn(out, k, kn);
758             } else {
759 0           encode_quoted_string(out, *ksv);
760             }
761 1           sv_catpvn(out, ": ", 2);
762              
763 1           SV **v = hv_fetch(hv, k, (I32)kn, 0);
764 1           encode_value_brace(out, *v, level + 1, opts);
765             }
766 1           sv_catpvn(out, "}", 1);
767 1           SvREFCNT_dec((SV*)keys);
768 1           return;
769             }
770              
771 1           sv_catpvn(out, "{\n", 2);
772 3 100         for (I32 i = 0; i < nkeys; i++) {
773 2 100         if (i > 0) sv_catpvn(out, ",\n", 2);
774 2           append_indent(out, level + 1, opts->indent);
775              
776 2           SV **ksv = av_fetch(keys, i, 0);
777             STRLEN kn;
778 2           const char *k = SvPV(*ksv, kn);
779              
780 2 50         if (sv_is_identifier_key(*ksv)) {
781 2           sv_catpvn(out, k, kn);
782             } else {
783 0           encode_quoted_string(out, *ksv);
784             }
785 2           sv_catpvn(out, ": ", 2);
786              
787 2           SV **v = hv_fetch(hv, k, (I32)kn, 0);
788 2           encode_value_brace(out, *v, level + 1, opts);
789             }
790 1           sv_catpvn(out, "\n", 1);
791 1           append_indent(out, level, opts->indent);
792 1           sv_catpvn(out, "}", 1);
793              
794 1           SvREFCNT_dec((SV*)keys);
795             }
796              
797 1           static void encode_array_brace(SV *out, AV *av, I32 level, brace_encode_opts_t *opts) {
798 1           I32 n = av_len(av) + 1;
799 1 50         if (n <= 0) {
800 0           sv_catpvn(out, "[]", 2);
801 0           return;
802             }
803              
804 1 50         if (!opts->pretty) {
805 0           sv_catpvn(out, "[", 1);
806 0 0         for (I32 i = 0; i < n; i++) {
807 0 0         if (i > 0) sv_catpvn(out, ", ", 2);
808 0           SV **item = av_fetch(av, i, 0);
809 0           encode_value_brace(out, *item, level + 1, opts);
810             }
811 0           sv_catpvn(out, "]", 1);
812 0           return;
813             }
814              
815 1           sv_catpvn(out, "[\n", 2);
816 3 100         for (I32 i = 0; i < n; i++) {
817 2 100         if (i > 0) sv_catpvn(out, ",\n", 2);
818 2           append_indent(out, level + 1, opts->indent);
819 2           SV **item = av_fetch(av, i, 0);
820 2           encode_value_brace(out, *item, level + 1, opts);
821             }
822 1           sv_catpvn(out, "\n", 1);
823 1           append_indent(out, level, opts->indent);
824 1           sv_catpvn(out, "]", 1);
825             }
826              
827 10           static void encode_value_brace(SV *out, SV *value, I32 level, brace_encode_opts_t *opts) {
828 10 50         if (!SvOK(value)) {
829 0           sv_catpvn(out, "null", 4);
830 9           return;
831             }
832              
833 10 100         if (SvROK(value)) {
834 6           SV *rv = SvRV(value);
835 6 50         if (SvOBJECT(rv)) {
836 0           bp_throw("Encoding blessed references is not supported");
837             }
838 6 100         if (SvTYPE(rv) == SVt_PVAV) {
839 1           encode_array_brace(out, (AV*)rv, level, opts);
840 1           return;
841             }
842 5 50         if (SvTYPE(rv) == SVt_PVHV) {
843 5           encode_hash_brace(out, (HV*)rv, level, opts);
844 5           return;
845             }
846 0           bp_throw("Encoding references of this type is not supported");
847             }
848              
849             STRLEN n;
850 4           const char *s = SvPV(value, n);
851 4 50         if (n == 4 && strncmp(s, "true", 4) == 0) {
    0          
852 0           sv_catpvn(out, "true", 4);
853 0           return;
854             }
855 4 50         if (n == 5 && strncmp(s, "false", 5) == 0) {
    0          
856 0           sv_catpvn(out, "false", 5);
857 0           return;
858             }
859              
860 4 100         if (sv_is_plain_numberish(value)) {
861             SV *num;
862 3 50         if (SvIOK(value) && !SvNOK(value)) {
    50          
863 3           num = newSViv(SvIV(value));
864             } else {
865 0           num = newSVnv(SvNV(value));
866             }
867 3           sv_catpv(out, SvPV_nolen(num));
868 3           SvREFCNT_dec(num);
869 3           return;
870             }
871              
872 1           encode_quoted_string(out, value);
873             }
874              
875 5           static void parse_brace_encode_opts(SV *opts_sv, brace_encode_opts_t *opts) {
876 5           opts->pretty = 0;
877 5           opts->canonical = 0;
878 5           opts->indent = 2;
879              
880 5 50         if (!opts_sv || !SvOK(opts_sv) || !SvROK(opts_sv) || SvTYPE(SvRV(opts_sv)) != SVt_PVHV) {
    50          
    50          
    50          
881 0           return;
882             }
883              
884 5           HV *hv = (HV*)SvRV(opts_sv);
885             SV **v;
886              
887 5           v = hv_fetch(hv, "pretty", 6, 0);
888 5 100         if (v) opts->pretty = SvTRUE(*v) ? 1 : 0;
889              
890 5           v = hv_fetch(hv, "canonical", 9, 0);
891 5 50         if (v) opts->canonical = SvTRUE(*v) ? 1 : 0;
892              
893 5           v = hv_fetch(hv, "indent", 6, 0);
894 5 100         if (v) {
895 3           IV iv = SvIV(*v);
896 3 50         opts->indent = iv > 0 ? (int)iv : 2;
897             }
898             }
899              
900 7           static SV *xs_decode_brace_impl(SV *text_sv) {
901             STRLEN len;
902 7           const char *text = SvPV(text_sv, len);
903              
904             brace_parser_t p;
905 7           p.text = text;
906 7           p.len = len;
907 7           p.pos = 0;
908              
909 7           bp_skip_ws(&p);
910 7           SV *value = bp_parse_value(&p);
911 6           bp_skip_ws(&p);
912              
913 6 50         if (p.pos < p.len) {
914 0           SvREFCNT_dec(value);
915 0           bp_throw("Trailing characters after document");
916             }
917              
918 6           return value;
919             }
920              
921 5           static SV *xs_encode_brace_impl(SV *data_sv, SV *opts_sv) {
922             brace_encode_opts_t opts;
923 5           parse_brace_encode_opts(opts_sv, &opts);
924              
925 5           SV *out = newSVpvn("", 0);
926 5           encode_value_brace(out, data_sv, 0, &opts);
927 5           return out;
928             }
929              
930             typedef struct {
931             AV *lines;
932             I32 pos;
933             I32 max_depth;
934             } line_decode_ctx_t;
935              
936             typedef struct {
937             I32 indent;
938             char delimiter;
939             I32 depth;
940             I32 max_depth;
941             HV *seen;
942             HV *priority;
943             } line_encode_ctx_t;
944              
945 1300           static int is_word_char(char c) {
946 1300 50         return (c == '_') || isalnum((unsigned char)c);
    100          
947             }
948              
949 179           static SV *line_trimmed_sv(SV *in) {
950             STRLEN n;
951 179           const char *s = SvPV(in, n);
952 179           STRLEN start = 0;
953 179 50         while (start < n && (s[start] == ' ' || s[start] == '\t')) start++;
    50          
    50          
954 179           STRLEN end = n;
955 179 50         while (end > start && (s[end - 1] == ' ' || s[end - 1] == '\t')) end--;
    50          
    50          
956 179           return newSVpvn(s + start, end - start);
957             }
958              
959 190           static SV *line_ltrimmed_sv(SV *in) {
960             STRLEN n;
961 190           const char *s = SvPV(in, n);
962 190           STRLEN start = 0;
963 10384 50         while (start < n && (s[start] == ' ' || s[start] == '\t')) start++;
    100          
    50          
964 190           return newSVpvn(s + start, n - start);
965             }
966              
967 441           static I32 line_depth_from_sv(SV *line) {
968             STRLEN n;
969 441           const char *s = SvPV(line, n);
970 441           STRLEN i = 0;
971 33077 50         while (i < n && s[i] == ' ') i++;
    100          
972 441           return (I32)(i / 2);
973             }
974              
975 53           static AV *line_split_lines(SV *text_sv) {
976             STRLEN n;
977 53           const char *s = SvPV(text_sv, n);
978 53           AV *lines = newAV();
979              
980 53           STRLEN start = 0;
981 127538 100         for (STRLEN i = 0; i < n; i++) {
982 127485 100         if (s[i] == '\n') {
983 213           STRLEN end = i;
984 213 50         if (end > start && s[end - 1] == '\r') end--;
    50          
985 213           av_push(lines, newSVpvn(s + start, end - start));
986 213           start = i + 1;
987             }
988             }
989 53 50         if (start <= n) {
990 53           av_push(lines, newSVpvn(s + start, n - start));
991             }
992              
993 53           I32 len = av_len(lines) + 1;
994 53 50         if (len > 0) {
995 53           SV **last = av_fetch(lines, len - 1, 0);
996 53 50         if (last) {
997             STRLEN ln;
998 53           const char *ls = SvPV(*last, ln);
999 53 100         if (ln == 0 || (ln == 1 && (ls[0] == '\r' || ls[0] == '\n'))) {
    100          
    50          
    50          
1000 17           av_pop(lines);
1001             }
1002             }
1003             }
1004 53           return lines;
1005             }
1006              
1007 39           static AV *line_split_char(SV *sv, char delimiter) {
1008             STRLEN n;
1009 39           const char *s = SvPV(sv, n);
1010 39           AV *out = newAV();
1011 39           STRLEN start = 0;
1012 430 100         for (STRLEN i = 0; i < n; i++) {
1013 391 100         if (s[i] == delimiter) {
1014 58           av_push(out, newSVpvn(s + start, i - start));
1015 58           start = i + 1;
1016             }
1017             }
1018 39 50         if (start < n) {
1019 39           av_push(out, newSVpvn(s + start, n - start));
1020 0           } else if (n == 0) {
1021             /* keep empty -> [] behavior like Perl split on empty string */
1022             }
1023 39           return out;
1024             }
1025              
1026 441           static int line_is_empty_or_ws(SV *sv) {
1027             STRLEN n;
1028 441           const char *s = SvPV(sv, n);
1029 33077 50         for (STRLEN i = 0; i < n; i++) {
1030 33077 100         if (!(s[i] == ' ' || s[i] == '\t' || s[i] == '\r' || s[i] == '\n')) return 0;
    50          
    50          
    50          
1031             }
1032 0           return 1;
1033             }
1034              
1035 53           static int line_parse_root_array_header(SV *trimmed, IV *count, char *delim, SV **rest) {
1036             STRLEN n;
1037 53           const char *s = SvPV(trimmed, n);
1038 53 100         if (n < 4 || s[0] != '[') return 0;
    100          
1039 3           STRLEN i = 1;
1040 3           STRLEN dstart = i;
1041 6 50         while (i < n && isdigit((unsigned char)s[i])) i++;
    100          
1042 3 50         if (i == dstart) return 0;
1043 3           SV *cnt = newSVpvn(s + dstart, i - dstart);
1044 3           *count = SvIV(cnt);
1045 3           SvREFCNT_dec(cnt);
1046              
1047 3           *delim = ',';
1048 3 50         if (i < n && (s[i] == '\t' || s[i] == '|')) {
    50          
    100          
1049 1           *delim = s[i];
1050 1           i++;
1051             }
1052 3 50         if (i >= n || s[i] != ']') return 0;
    50          
1053 3           i++;
1054 3 50         while (i < n && (s[i] == ' ' || s[i] == '\t')) i++;
    50          
    50          
1055 3 50         if (i >= n || s[i] != ':') return 0;
    50          
1056 3           i++;
1057 6 50         while (i < n && (s[i] == ' ' || s[i] == '\t')) i++;
    100          
    50          
1058 3           *rest = newSVpvn(s + i, n - i);
1059 3           return 1;
1060             }
1061              
1062 119           static SV *line_parse_primitive(SV *value_sv) {
1063 119           SV *trimmed = line_trimmed_sv(value_sv);
1064             STRLEN n;
1065 119           const char *s = SvPV(trimmed, n);
1066              
1067 119 100         if (n >= 2 && s[0] == '"' && s[n - 1] == '"') {
    100          
    100          
1068 7           SV *out = newSVpvn("", 0);
1069 102520 100         for (STRLEN i = 1; i + 1 < n; i++) {
1070 102513           char c = s[i];
1071 102517 100         if (c == '\\' && i + 2 < n) {
    50          
1072 4           char e = s[++i];
1073 4           switch (e) {
1074 2           case '"': sv_catpvn(out, "\"", 1); break;
1075 0           case '\\': sv_catpvn(out, "\\", 1); break;
1076 1           case 'n': sv_catpvn(out, "\n", 1); break;
1077 0           case 'r': sv_catpvn(out, "\r", 1); break;
1078 0           case 't': sv_catpvn(out, "\t", 1); break;
1079 1           default:
1080 1           sv_catpvn(out, "\\", 1);
1081 1           sv_catpvn(out, &e, 1);
1082 1           break;
1083             }
1084             } else {
1085 102509           sv_catpvn(out, &c, 1);
1086             }
1087             }
1088 7           SvREFCNT_dec(trimmed);
1089 7           return out;
1090             }
1091              
1092 112 100         if (n == 4 && strncmp(s, "null", 4) == 0) {
    100          
1093 1           SvREFCNT_dec(trimmed);
1094 1           return newSV(0);
1095             }
1096 111 100         if (n == 4 && strncmp(s, "true", 4) == 0) {
    100          
1097 2           SvREFCNT_dec(trimmed);
1098 2           return newSViv(1);
1099             }
1100 109 100         if (n == 5 && strncmp(s, "false", 5) == 0) {
    100          
1101 1           SvREFCNT_dec(trimmed);
1102 1           return newSViv(0);
1103             }
1104              
1105 108 100         if (is_numeric_token(s, n)) {
1106 41           STRLEN i = 0;
1107 41 50         if (i < n && (s[i] == '+' || s[i] == '-')) i++;
    50          
    100          
1108 41 100         if (i + 1 < n && s[i] == '0' && isdigit((unsigned char)s[i + 1])) {
    100          
    50          
1109 1 50         if (!(i + 1 < n && s[i + 1] == '.')) {
    50          
1110 1           SV *as_str = newSVsv(trimmed);
1111 1           SvREFCNT_dec(trimmed);
1112 1           return as_str;
1113             }
1114             }
1115              
1116 40 100         int has_float = (memchr(s, '.', n) || memchr(s, 'e', n) || memchr(s, 'E', n)) ? 1 : 0;
    100          
    50          
1117 40 100         if (has_float) {
1118 5           NV nv = SvNV(trimmed);
1119 5 50         if (nv == 0.0) {
1120 0           SvREFCNT_dec(trimmed);
1121 0           return newSViv(0);
1122             }
1123             char buf[64];
1124 5           snprintf(buf, sizeof(buf), "%.15g", (double)nv);
1125 5           SV *tmp = newSVpv(buf, 0);
1126 5           NV nn = SvNV(tmp);
1127 5           SvREFCNT_dec(tmp);
1128 5           SvREFCNT_dec(trimmed);
1129 5 100         if ((IV)nn == nn) return newSViv((IV)nn);
1130 4           return newSVnv(nn);
1131             } else {
1132 35           IV iv = SvIV(trimmed);
1133 35           SvREFCNT_dec(trimmed);
1134 35           return newSViv(iv);
1135             }
1136             }
1137              
1138 67           return trimmed;
1139             }
1140              
1141 165           static int line_parse_object_header(SV *trimmed, SV **key, SV **bracket, SV **fields, SV **rest) {
1142             STRLEN n;
1143 165           const char *s = SvPV(trimmed, n);
1144 165           STRLEN i = 0;
1145 165 50         if (i >= n || !is_word_char(s[i])) return 0;
    50          
1146 165           STRLEN kstart = i;
1147 1138 100         while (i < n && is_word_char(s[i])) i++;
    100          
1148 165           *key = newSVpvn(s + kstart, i - kstart);
1149              
1150 165           *bracket = &PL_sv_undef;
1151 165           *fields = &PL_sv_undef;
1152              
1153 165 100         if (i < n && s[i] == '[') {
    100          
1154 19           STRLEN bs = i;
1155 19           i++;
1156 46 50         while (i < n && s[i] != ']') i++;
    100          
1157 19 50         if (i >= n) return 0;
1158 19           i++;
1159 19           *bracket = newSVpvn(s + bs, i - bs);
1160             }
1161              
1162 165 100         if (i < n && s[i] == '{') {
    100          
1163 11           STRLEN fs = i;
1164 11           i++;
1165 122 50         while (i < n && s[i] != '}') i++;
    100          
1166 11 50         if (i >= n) return 0;
1167 11           i++;
1168 11           *fields = newSVpvn(s + fs, i - fs);
1169             }
1170              
1171 165 100         while (i < n && (s[i] == ' ' || s[i] == '\t')) i++;
    50          
    50          
1172 165 100         if (i >= n || s[i] != ':') return 0;
    100          
1173 160           i++;
1174 200 100         while (i < n && (s[i] == ' ' || s[i] == '\t')) i++;
    100          
    50          
1175 160           *rest = newSVpvn(s + i, n - i);
1176 160           return 1;
1177             }
1178              
1179             static SV *line_decode_object(line_decode_ctx_t *ctx, I32 target_depth);
1180             static SV *line_decode_array_value(line_decode_ctx_t *ctx, SV *bracket, SV *fields, SV *rest);
1181              
1182 146           static SV *line_decode_object(line_decode_ctx_t *ctx, I32 target_depth) {
1183 146 100         if (target_depth > ctx->max_depth) {
1184 1           croak("Maximum nesting depth exceeded (max: %ld)", (long)ctx->max_depth);
1185             }
1186              
1187 145           HV *obj = newHV();
1188 145           I32 total = av_len(ctx->lines) + 1;
1189 200 100         while (ctx->pos < total) {
1190 156           SV **linep = av_fetch(ctx->lines, ctx->pos, 0);
1191 158 50         if (!linep) { ctx->pos++; continue; }
1192 156 50         if (line_is_empty_or_ws(*linep)) { ctx->pos++; continue; }
1193              
1194 156           I32 depth = line_depth_from_sv(*linep);
1195 156 50         if (depth < target_depth) break;
1196 156 50         if (depth > target_depth + 1) { ctx->pos++; continue; }
1197 156 50         if (depth > target_depth) break;
1198              
1199 156           ctx->pos++;
1200 156           SV *trim = line_ltrimmed_sv(*linep);
1201 156           SV *key = &PL_sv_undef, *br = &PL_sv_undef, *fs = &PL_sv_undef, *rest = &PL_sv_undef;
1202 156           int ok = line_parse_object_header(trim, &key, &br, &fs, &rest);
1203 156           SvREFCNT_dec(trim);
1204 156 100         if (!ok) {
1205 2 50         if (key != &PL_sv_undef) SvREFCNT_dec(key);
1206 2           continue;
1207             }
1208              
1209             SV *value;
1210 154 100         if (br != &PL_sv_undef) {
1211 18           value = line_decode_array_value(ctx, br, fs, rest);
1212             } else {
1213 136 100         if (SvCUR(rest) == 0) {
1214 106           value = line_decode_object(ctx, target_depth + 1);
1215             } else {
1216 30           value = line_parse_primitive(rest);
1217             }
1218             }
1219              
1220             STRLEN kn;
1221 53           const char *ks = SvPV(key, kn);
1222 53           hv_store(obj, ks, (I32)kn, value, 0);
1223              
1224 53           SvREFCNT_dec(key);
1225 53 100         if (br != &PL_sv_undef) SvREFCNT_dec(br);
1226 53 100         if (fs != &PL_sv_undef) SvREFCNT_dec(fs);
1227 53           SvREFCNT_dec(rest);
1228             }
1229 44           return newRV_noinc((SV*)obj);
1230             }
1231              
1232 18           static int line_parse_bracket_info(SV *bracket, IV *count, char *delimiter) {
1233             STRLEN n;
1234 18           const char *s = SvPV(bracket, n);
1235 18 50         if (n < 3 || s[0] != '[') return 0;
    50          
1236 18           STRLEN i = 1;
1237 18           STRLEN start = i;
1238 35 50         while (i < n && isdigit((unsigned char)s[i])) i++;
    100          
1239 18 100         if (i == start) return 0;
1240 17           SV *cnt = newSVpvn(s + start, i - start);
1241 17           *count = SvIV(cnt);
1242 17           SvREFCNT_dec(cnt);
1243 17           *delimiter = ',';
1244 17 50         if (i < n && (s[i] == '\t' || s[i] == '|')) {
    100          
    100          
1245 7           *delimiter = s[i];
1246 7           i++;
1247             }
1248 17 50         if (i >= n || s[i] != ']') return 0;
    50          
1249 17           return 1;
1250             }
1251              
1252 18           static SV *line_decode_array_value(line_decode_ctx_t *ctx, SV *bracket, SV *fields, SV *rest) {
1253 18           IV count = 0;
1254 18           char delimiter = ',';
1255 18 100         if (!line_parse_bracket_info(bracket, &count, &delimiter)) {
1256 1           return newRV_noinc((SV*)newAV());
1257             }
1258              
1259 17 100         if (fields != &PL_sv_undef) {
1260             STRLEN fn;
1261 11           const char *fs = SvPV(fields, fn);
1262 11 50         if (fn < 2) return newRV_noinc((SV*)newAV());
1263 11           SV *fields_inner = newSVpvn(fs + 1, fn - 2);
1264 11           AV *field_names = line_split_char(fields_inner, delimiter);
1265 11           SvREFCNT_dec(fields_inner);
1266              
1267 11           AV *rows = newAV();
1268 11           I32 total = av_len(ctx->lines) + 1;
1269 33 100         while (ctx->pos < total) {
1270 22           SV **linep = av_fetch(ctx->lines, ctx->pos, 0);
1271 22 50         if (!linep) { ctx->pos++; continue; }
1272 22 50         if (line_is_empty_or_ws(*linep)) { ctx->pos++; continue; }
1273              
1274 22           I32 depth = line_depth_from_sv(*linep);
1275 22 50         if (depth <= 0) break;
1276              
1277 22           SV *trim = line_ltrimmed_sv(*linep);
1278 22           STRLEN tn; const char *ts = SvPV(trim, tn);
1279 22 50         if (tn >= 1 && ts[0] == '-') {
    50          
1280 0           SvREFCNT_dec(trim);
1281 0           break;
1282             }
1283              
1284 22           ctx->pos++;
1285 22           AV *vals = line_split_char(trim, delimiter);
1286 22           HV *row = newHV();
1287              
1288 22           I32 nf = av_len(field_names) + 1;
1289 22           I32 nv = av_len(vals) + 1;
1290 74 100         for (I32 i = 0; i < nf && i < nv; i++) {
    100          
1291 52           SV **f = av_fetch(field_names, i, 0);
1292 52           SV **v = av_fetch(vals, i, 0);
1293 52           SV *pv = line_parse_primitive(*v);
1294 52           STRLEN klen; const char *k = SvPV(*f, klen);
1295 52           hv_store(row, k, (I32)klen, pv, 0);
1296             }
1297              
1298 22           av_push(rows, newRV_noinc((SV*)row));
1299 22           SvREFCNT_dec((SV*)vals);
1300 22           SvREFCNT_dec(trim);
1301             }
1302              
1303 11           SvREFCNT_dec((SV*)field_names);
1304 11           return newRV_noinc((SV*)rows);
1305             }
1306              
1307 6           int has_list = 0;
1308 6           I32 peek = ctx->pos;
1309 6           I32 total = av_len(ctx->lines) + 1;
1310 6 100         while (peek < total) {
1311 3           SV **linep = av_fetch(ctx->lines, peek, 0);
1312 3 50         if (!linep) { peek++; continue; }
1313 3 50         if (line_is_empty_or_ws(*linep)) { peek++; continue; }
1314 3           I32 d = line_depth_from_sv(*linep);
1315 6 50         if (d <= 0) break;
1316 3           SV *trim = line_ltrimmed_sv(*linep);
1317 3           STRLEN tn; const char *ts = SvPV(trim, tn);
1318 3 50         if (tn >= 1 && ts[0] == '-') has_list = 1;
    50          
1319 3           SvREFCNT_dec(trim);
1320 3           break;
1321             }
1322              
1323 6 100         if (has_list) {
1324 3           AV *items = newAV();
1325 10 100         while (ctx->pos < total) {
1326 7           SV **linep = av_fetch(ctx->lines, ctx->pos, 0);
1327 7 50         if (!linep) { ctx->pos++; continue; }
1328 7 50         if (line_is_empty_or_ws(*linep)) { ctx->pos++; continue; }
1329              
1330 7           I32 depth = line_depth_from_sv(*linep);
1331 7 50         if (depth <= 0) break;
1332 7           SV *trim = line_ltrimmed_sv(*linep);
1333 7           STRLEN tn; const char *ts = SvPV(trim, tn);
1334              
1335 7 50         if (!(tn >= 2 && ts[0] == '-' && ts[1] == ' ')) {
    50          
    50          
1336 0           SvREFCNT_dec(trim);
1337 0           break;
1338             }
1339 7           ctx->pos++;
1340 7           SV *item_content = newSVpvn(ts + 2, tn - 2);
1341 7           SV *item_trim = line_trimmed_sv(item_content);
1342 7           SvREFCNT_dec(item_content);
1343              
1344 7           SV *key = &PL_sv_undef, *br = &PL_sv_undef, *fs = &PL_sv_undef, *restv = &PL_sv_undef;
1345 11 100         if (line_parse_object_header(item_trim, &key, &br, &fs, &restv) && br == &PL_sv_undef) {
    50          
1346 4           HV *item = newHV();
1347             SV *first_val;
1348 4 50         if (SvCUR(restv) == 0) {
1349 0           first_val = line_decode_object(ctx, depth + 2);
1350             } else {
1351 4           first_val = line_parse_primitive(restv);
1352             }
1353 4           STRLEN kn; const char *ks = SvPV(key, kn);
1354 4           hv_store(item, ks, (I32)kn, first_val, 0);
1355              
1356 6 100         while (ctx->pos < total) {
1357 4           SV **nextp = av_fetch(ctx->lines, ctx->pos, 0);
1358 4 50         if (!nextp) { ctx->pos++; continue; }
1359 4 50         if (line_is_empty_or_ws(*nextp)) { ctx->pos++; continue; }
1360 4           I32 nd = line_depth_from_sv(*nextp);
1361 4 100         if (nd < depth + 1 || nd > depth + 1) break;
    50          
1362 2           SV *ntrim = line_ltrimmed_sv(*nextp);
1363 2           STRLEN nn; const char *ns = SvPV(ntrim, nn);
1364 2 50         if (nn >= 1 && ns[0] == '-') { SvREFCNT_dec(ntrim); break; }
    50          
1365              
1366 2           SV *k2 = &PL_sv_undef, *b2 = &PL_sv_undef, *f2 = &PL_sv_undef, *r2 = &PL_sv_undef;
1367 2 50         if (!line_parse_object_header(ntrim, &k2, &b2, &f2, &r2) || b2 != &PL_sv_undef) {
    50          
1368 0 0         if (k2 != &PL_sv_undef) SvREFCNT_dec(k2);
1369 0           SvREFCNT_dec(ntrim);
1370 0           break;
1371             }
1372 2           ctx->pos++;
1373 2           SV *pv = line_parse_primitive(r2);
1374 2           STRLEN k2n; const char *k2s = SvPV(k2, k2n);
1375 2           hv_store(item, k2s, (I32)k2n, pv, 0);
1376 2           SvREFCNT_dec(k2);
1377 2           SvREFCNT_dec(r2);
1378 2           SvREFCNT_dec(ntrim);
1379             }
1380              
1381 4           av_push(items, newRV_noinc((SV*)item));
1382 4           SvREFCNT_dec(key);
1383 4           SvREFCNT_dec(restv);
1384             } else {
1385 3           SV *pv = line_parse_primitive(item_trim);
1386 3           av_push(items, pv);
1387 3 50         if (key != &PL_sv_undef) SvREFCNT_dec(key);
1388 3 50         if (restv != &PL_sv_undef) SvREFCNT_dec(restv);
1389             }
1390 7           SvREFCNT_dec(item_trim);
1391             }
1392 3           return newRV_noinc((SV*)items);
1393             }
1394              
1395 3           AV *vals = line_split_char(rest, delimiter);
1396 3           AV *out = newAV();
1397 3           I32 nv = av_len(vals) + 1;
1398 12 100         for (I32 i = 0; i < nv; i++) {
1399 9           SV **v = av_fetch(vals, i, 0);
1400 9           av_push(out, line_parse_primitive(*v));
1401             }
1402 3           SvREFCNT_dec((SV*)vals);
1403 3           return newRV_noinc((SV*)out);
1404             }
1405              
1406 53           static SV *xs_decode_line_impl(SV *text_sv, SV *opts_sv) {
1407             line_decode_ctx_t ctx;
1408 53           ctx.lines = line_split_lines(text_sv);
1409 53           ctx.pos = 0;
1410 53           ctx.max_depth = 100;
1411              
1412 53 50         if (opts_sv && SvOK(opts_sv) && SvROK(opts_sv) && SvTYPE(SvRV(opts_sv)) == SVt_PVHV) {
    50          
    50          
    50          
1413 53           HV *hv = (HV*)SvRV(opts_sv);
1414 53           SV **md = hv_fetch(hv, "max_depth", 9, 0);
1415 53 50         if (md) ctx.max_depth = (I32)SvIV(*md);
1416             }
1417              
1418 53           AV *non_empty = newAV();
1419 53           I32 total = av_len(ctx.lines) + 1;
1420 302 100         for (I32 i = 0; i < total; i++) {
1421 249           SV **linep = av_fetch(ctx.lines, i, 0);
1422 249 50         if (!linep || line_is_empty_or_ws(*linep)) continue;
    50          
1423 249 100         if (line_depth_from_sv(*linep) != 0) continue;
1424 60           av_push(non_empty, newSVsv(*linep));
1425             }
1426              
1427 53           I32 nn = av_len(non_empty) + 1;
1428 53 50         if (nn <= 0) {
1429 0           SvREFCNT_dec((SV*)non_empty);
1430 0           SvREFCNT_dec((SV*)ctx.lines);
1431 0           return newRV_noinc((SV*)newHV());
1432             }
1433              
1434 53           SV **firstp = av_fetch(non_empty, 0, 0);
1435 53           SV *first_trim = line_trimmed_sv(*firstp);
1436              
1437 53           IV count = 0;
1438 53           char delimiter = ',';
1439 53           SV *rest = &PL_sv_undef;
1440 53 100         if (line_parse_root_array_header(first_trim, &count, &delimiter, &rest)) {
1441 3 50         if (SvCUR(rest) > 0) {
1442 3           AV *vals = line_split_char(rest, delimiter);
1443 3           AV *out = newAV();
1444 3           I32 nv = av_len(vals) + 1;
1445 12 100         for (I32 i = 0; i < nv; i++) {
1446 9           SV **v = av_fetch(vals, i, 0);
1447 9           av_push(out, line_parse_primitive(*v));
1448             }
1449 3           SvREFCNT_dec((SV*)vals);
1450 3           SvREFCNT_dec(rest);
1451 3           SvREFCNT_dec(first_trim);
1452 3           SvREFCNT_dec((SV*)non_empty);
1453 3           SvREFCNT_dec((SV*)ctx.lines);
1454 3           return newRV_noinc((SV*)out);
1455             }
1456 0           SvREFCNT_dec(rest);
1457 0           ctx.pos = 0;
1458 0           AV *items = newAV();
1459 0 0         while (ctx.pos < total) {
1460 0           SV **linep = av_fetch(ctx.lines, ctx.pos, 0);
1461 0 0         if (!linep || line_is_empty_or_ws(*linep)) { ctx.pos++; continue; }
    0          
1462 0           I32 d = line_depth_from_sv(*linep);
1463 0 0         if (d == 0) {
1464 0           SV *trim = line_ltrimmed_sv(*linep);
1465 0           STRLEN tn; const char *ts = SvPV(trim, tn);
1466 0           SvREFCNT_dec(trim);
1467 0 0         if (tn >= 1 && ts[0] == '[') { ctx.pos++; continue; }
    0          
1468 0 0         } else if (d > 0) {
1469 0           SV *trim = line_ltrimmed_sv(*linep);
1470 0           STRLEN tn; const char *ts = SvPV(trim, tn);
1471 0 0         if (tn >= 1 && ts[0] == '-') {
    0          
1472 0           ctx.pos++;
1473 0           SV *ic = newSVpvn(ts + 1, tn - 1);
1474 0           av_push(items, line_parse_primitive(ic));
1475 0           SvREFCNT_dec(ic);
1476 0           SvREFCNT_dec(trim);
1477 0           continue;
1478             }
1479 0           SvREFCNT_dec(trim);
1480             }
1481 0           ctx.pos++;
1482             }
1483 0           SvREFCNT_dec(first_trim);
1484 0           SvREFCNT_dec((SV*)non_empty);
1485 0           SvREFCNT_dec((SV*)ctx.lines);
1486 0           return newRV_noinc((SV*)items);
1487             }
1488              
1489 50           STRLEN fn; const char *fs = SvPV(first_trim, fn);
1490 50           int first_word_bracket = 0;
1491 268 100         for (STRLEN i = 0; i < fn; i++) {
1492 258 100         if (fs[i] == '[') { first_word_bracket = 1; break; }
1493 239 100         if (fs[i] == ':' || fs[i] == ' ') break;
    50          
1494             }
1495              
1496             SV *result;
1497 50 100         if (first_word_bracket) {
1498 19           ctx.pos = 0;
1499 19           result = line_decode_object(&ctx, 0);
1500 31 100         } else if (nn == 1 && !memchr(fs, ':', fn)) {
    100          
1501 10           result = line_parse_primitive(first_trim);
1502             } else {
1503 21           ctx.pos = 0;
1504 21           result = line_decode_object(&ctx, 0);
1505             }
1506              
1507 49           SvREFCNT_dec(first_trim);
1508 49           SvREFCNT_dec((SV*)non_empty);
1509 49           SvREFCNT_dec((SV*)ctx.lines);
1510 49           return result;
1511             }
1512              
1513 62           static int line_needs_quoting(SV *sv, char delimiter) {
1514             STRLEN n;
1515 62           const char *s = SvPV(sv, n);
1516 62 50         if (n == 0) return 1;
1517 62 50         if (s[0] == ' ' || s[n - 1] == ' ') return 1;
    50          
1518 62 100         if ((n == 4 && strncmp(s, "true", 4) == 0) ||
    50          
1519 62 100         (n == 5 && strncmp(s, "false", 5) == 0) ||
    50          
1520 62 100         (n == 4 && strncmp(s, "null", 4) == 0)) return 1;
    50          
1521 62 50         if (is_numeric_token(s, n)) return 1;
1522 62 100         if (n >= 2 && s[0] == '0' && isdigit((unsigned char)s[1])) return 1;
    50          
    0          
1523 367 100         for (STRLEN i = 0; i < n; i++) {
1524 305           char c = s[i];
1525 305 50         if (c == ':' || c == '"' || c == '\\' || c == '[' || c == ']' || c == '{' || c == '}' || c == '-' ||
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
1526 305 50         c == '\r' || c == '\n' || c == '\t' || c == delimiter) return 1;
    50          
    50          
1527             }
1528 62 50         if (s[0] == '-') return 1;
1529 62           return 0;
1530             }
1531              
1532 0           static SV *line_escape_string(SV *sv) {
1533             STRLEN n;
1534 0           const char *s = SvPV(sv, n);
1535 0           SV *out = newSVpvn("", 0);
1536 0 0         for (STRLEN i = 0; i < n; i++) {
1537 0           char c = s[i];
1538 0           switch (c) {
1539 0           case '\\': sv_catpvn(out, "\\\\", 2); break;
1540 0           case '"': sv_catpvn(out, "\\\"", 2); break;
1541 0           case '\n': sv_catpvn(out, "\\n", 2); break;
1542 0           case '\r': sv_catpvn(out, "\\r", 2); break;
1543 0           case '\t': sv_catpvn(out, "\\t", 2); break;
1544 0           default: sv_catpvn(out, &c, 1); break;
1545             }
1546             }
1547 0           return out;
1548             }
1549              
1550 119           static SV *line_encode_primitive(SV *value, char delimiter) {
1551 119 50         if (!SvOK(value)) return newSVpv("null", 0);
1552              
1553 119 100         if (sv_is_plain_numberish(value)) {
1554 57           NV nv = SvNV(value);
1555 57 100         if (nv == 0.0) return newSVpv("0", 0);
1556 56 100         if (SvIOK(value) && !SvNOK(value)) return newSViv(SvIV(value));
    50          
1557             char buf[64];
1558 56           snprintf(buf, sizeof(buf), "%.15g", (double)nv);
1559 56           return newSVpv(buf, 0);
1560             }
1561              
1562 62 50         if (line_needs_quoting(value, delimiter)) {
1563 0           SV *esc = line_escape_string(value);
1564 0           SV *out = newSVpv("\"", 0);
1565 0           sv_catpv(out, SvPV_nolen(esc));
1566 0           sv_catpvn(out, "\"", 1);
1567 0           SvREFCNT_dec(esc);
1568 0           return out;
1569             }
1570 62           return newSVsv(value);
1571             }
1572              
1573 280           static IV line_priority_rank(line_encode_ctx_t *ctx, SV *key) {
1574 280 100         if (!ctx->priority) return 999999;
1575 138           STRLEN n; const char *s = SvPV(key, n);
1576 138           SV **v = hv_fetch(ctx->priority, s, (I32)n, 0);
1577 138 100         if (!v) return 999999;
1578 90           return SvIV(*v);
1579             }
1580              
1581 105           static AV *line_sorted_keys(HV *hv, line_encode_ctx_t *ctx) {
1582 105           I32 count = hv_iterinit(hv);
1583 105 100         SV **arr = (SV**)safemalloc(sizeof(SV*) * (count > 0 ? count : 1));
1584 105           I32 idx = 0;
1585             HE *he;
1586 312 100         while ((he = hv_iternext(hv)) != NULL) {
1587 207           arr[idx++] = newSVsv(hv_iterkeysv(he));
1588             }
1589              
1590 312 100         for (I32 i = 0; i < idx; i++) {
1591 347 100         for (I32 j = i + 1; j < idx; j++) {
1592 140           IV ri = line_priority_rank(ctx, arr[i]);
1593 140           IV rj = line_priority_rank(ctx, arr[j]);
1594 140           int swap = 0;
1595 140 100         if (ri != rj) {
1596 58           swap = ri > rj;
1597             } else {
1598             STRLEN ni, nj;
1599 82           const char *si = SvPV(arr[i], ni);
1600 82           const char *sj = SvPV(arr[j], nj);
1601 82           STRLEN mn = ni < nj ? ni : nj;
1602 82           int c = memcmp(si, sj, mn);
1603 82 50         if (c == 0) c = (ni < nj) ? -1 : ((ni > nj) ? 1 : 0);
    0          
1604 82           swap = c > 0;
1605             }
1606 140 100         if (swap) {
1607 75           SV *tmp = arr[i];
1608 75           arr[i] = arr[j];
1609 75           arr[j] = tmp;
1610             }
1611             }
1612             }
1613              
1614 105           AV *keys = newAV();
1615 312 100         for (I32 i = 0; i < idx; i++) {
1616 207           av_push(keys, arr[i]);
1617             }
1618 105           safefree(arr);
1619 105           return keys;
1620             }
1621              
1622 91           static void line_append_line(SV *out, SV *line, int *first) {
1623 91 100         if (!*first) sv_catpvn(out, "\n", 1);
1624 91           *first = 0;
1625 91           sv_catpv(out, SvPV_nolen(line));
1626 91           }
1627              
1628             static SV *line_encode_value(line_encode_ctx_t *ctx, SV *value);
1629              
1630 2           static SV *line_encode_array(line_encode_ctx_t *ctx, AV *arr) {
1631 2           I32 n = av_len(arr) + 1;
1632 2           int all_prims = 1;
1633 8 100         for (I32 i = 0; i < n; i++) {
1634 6           SV **it = av_fetch(arr, i, 0);
1635 6 50         if (it && SvROK(*it)) { all_prims = 0; break; }
    50          
1636             }
1637              
1638 2 50         if (all_prims) {
1639 2           SV *out = newSVpvf("[%ld", (long)n);
1640 2 50         if (ctx->delimiter == '\t') sv_catpvn(out, "\t", 1);
1641 2 50         else if (ctx->delimiter == '|') sv_catpvn(out, "|", 1);
1642 2           sv_catpvn(out, "]: ", 3);
1643 8 100         for (I32 i = 0; i < n; i++) {
1644 6 100         if (i > 0) {
1645 4           char d = ctx->delimiter;
1646 4           sv_catpvn(out, &d, 1);
1647             }
1648 6           SV **it = av_fetch(arr, i, 0);
1649 6           SV *p = line_encode_primitive(*it, ctx->delimiter);
1650 6           sv_catpv(out, SvPV_nolen(p));
1651 6           SvREFCNT_dec(p);
1652             }
1653 2           return out;
1654             }
1655              
1656 0           SV *out = newSVpvf("[%ld]:", (long)n);
1657 0 0         for (I32 i = 0; i < n; i++) {
1658 0           SV **it = av_fetch(arr, i, 0);
1659 0           SV *enc = line_encode_value(ctx, *it);
1660 0 0         if (SvOK(enc) && SvCUR(enc) > 0) {
    0          
1661 0           sv_catpvn(out, "\n - ", 5);
1662 0           sv_catpv(out, SvPV_nolen(enc));
1663             }
1664 0           SvREFCNT_dec(enc);
1665             }
1666 0           return out;
1667             }
1668              
1669 18           static SV *line_encode_object_with_array(line_encode_ctx_t *ctx, const char *indent, SV *key, AV *arr) {
1670 18           I32 n = av_len(arr) + 1;
1671 18           int all_objects = (n > 0);
1672 47 100         for (I32 i = 0; i < n; i++) {
1673 30           SV **it = av_fetch(arr, i, 0);
1674 30 50         if (!it || !SvROK(*it) || SvTYPE(SvRV(*it)) != SVt_PVHV) { all_objects = 0; break; }
    100          
    50          
1675             }
1676              
1677 18           SV *out = newSVpvn("", 0);
1678 18           STRLEN kn; const char *ks = SvPV(key, kn);
1679              
1680 18 100         if (all_objects && n > 0) {
    50          
1681 17           HV *first = (HV*)SvRV(*av_fetch(arr, 0, 0));
1682 17           AV *first_keys = line_sorted_keys(first, ctx);
1683 17           I32 nk = av_len(first_keys) + 1;
1684 17           int can_tabular = 1;
1685              
1686 46 100         for (I32 i = 0; i < n && can_tabular; i++) {
    50          
1687 29           HV *row = (HV*)SvRV(*av_fetch(arr, i, 0));
1688 29           AV *rk = line_sorted_keys(row, ctx);
1689 29 100         if (av_len(rk) + 1 != nk) can_tabular = 0;
1690 101 100         for (I32 j = 0; j < nk && can_tabular; j++) {
    100          
1691 72           SV **a = av_fetch(first_keys, j, 0);
1692 72           SV **b = av_fetch(rk, j, 0);
1693 72           STRLEN an,bn; const char *as = SvPV(*a, an); const char *bs = SvPV(*b, bn);
1694 72 50         if (an != bn || memcmp(as, bs, an) != 0) can_tabular = 0;
    50          
1695             }
1696 29           hv_iterinit(row); HE *he;
1697 97 100         while ((he = hv_iternext(row)) != NULL) {
1698 72           SV *v = hv_iterval(row, he);
1699 72 100         if (SvROK(v)) { can_tabular = 0; break; }
1700             }
1701 29           SvREFCNT_dec((SV*)rk);
1702             }
1703              
1704 17 100         if (can_tabular) {
1705 12           sv_catpvf(out, "%s%s[%ld", indent, ks, (long)n);
1706 12 100         if (ctx->delimiter == '\t') sv_catpvn(out, "\t", 1);
1707 11 100         else if (ctx->delimiter == '|') sv_catpvn(out, "|", 1);
1708 12           sv_catpvn(out, "]{", 2);
1709 44 100         for (I32 i = 0; i < nk; i++) {
1710 32 100         if (i > 0) {
1711 20           char d = ctx->delimiter;
1712 20           sv_catpvn(out, &d, 1);
1713             }
1714 32           SV **f = av_fetch(first_keys, i, 0);
1715 32           sv_catpv(out, SvPV_nolen(*f));
1716             }
1717 12           sv_catpvn(out, "}:", 2);
1718              
1719 12           ctx->depth++;
1720 12           SV *row_indent = newSVpvn("", 0);
1721 36 100         for (I32 x = 0; x < ctx->depth * ctx->indent; x++) sv_catpvn(row_indent, " ", 1);
1722 35 100         for (I32 i = 0; i < n; i++) {
1723 23           HV *row = (HV*)SvRV(*av_fetch(arr, i, 0));
1724 23           sv_catpvn(out, "\n", 1);
1725 23           sv_catpv(out, SvPV_nolen(row_indent));
1726 84 100         for (I32 j = 0; j < nk; j++) {
1727 61 100         if (j > 0) {
1728 38           char d = ctx->delimiter;
1729 38           sv_catpvn(out, &d, 1);
1730             }
1731 61           SV **f = av_fetch(first_keys, j, 0);
1732 61           STRLEN fn; const char *fname = SvPV(*f, fn);
1733 61           SV **v = hv_fetch(row, fname, (I32)fn, 0);
1734 61           SV *pv = line_encode_primitive(*v, ctx->delimiter);
1735 61           sv_catpv(out, SvPV_nolen(pv));
1736 61           SvREFCNT_dec(pv);
1737             }
1738             }
1739 12           SvREFCNT_dec(row_indent);
1740 12           ctx->depth--;
1741 12           SvREFCNT_dec((SV*)first_keys);
1742 12           return out;
1743             }
1744 5           SvREFCNT_dec((SV*)first_keys);
1745             }
1746              
1747 6           sv_catpvf(out, "%s%s[%ld]:", indent, ks, (long)n);
1748 6           ctx->depth++;
1749 6           SV *item_indent = newSVpvn("", 0);
1750 26 100         for (I32 x = 0; x < ctx->depth * ctx->indent; x++) sv_catpvn(item_indent, " ", 1);
1751 6           SV *field_indent = newSVpvn("", 0);
1752 38 100         for (I32 x = 0; x < (ctx->depth + 1) * ctx->indent; x++) sv_catpvn(field_indent, " ", 1);
1753              
1754 6 100         if (all_objects && n > 0) {
    50          
1755 11 100         for (I32 i = 0; i < n; i++) {
1756 6           HV *row = (HV*)SvRV(*av_fetch(arr, i, 0));
1757 6           AV *keys = line_sorted_keys(row, ctx);
1758 6           I32 nk = av_len(keys) + 1;
1759 6 50         if (nk > 0) {
1760 6           SV **k0 = av_fetch(keys, 0, 0);
1761 6           STRLEN k0n; const char *k0s = SvPV(*k0, k0n);
1762 6           SV **v0 = hv_fetch(row, k0s, (I32)k0n, 0);
1763 6           ctx->depth++;
1764 6           SV *fv = line_encode_value(ctx, *v0);
1765 6           ctx->depth--;
1766 6           sv_catpvn(out, "\n", 1);
1767 6           sv_catpv(out, SvPV_nolen(item_indent));
1768 6           sv_catpvf(out, "- %s: %s", k0s, SvPV_nolen(fv));
1769 6           SvREFCNT_dec(fv);
1770 13 100         for (I32 j = 1; j < nk; j++) {
1771 7           SV **kj = av_fetch(keys, j, 0);
1772 7           STRLEN kjn; const char *kjs = SvPV(*kj, kjn);
1773 7           SV **vj = hv_fetch(row, kjs, (I32)kjn, 0);
1774 7           SV *ev = line_encode_value(ctx, *vj);
1775 7           sv_catpvn(out, "\n", 1);
1776 7           sv_catpv(out, SvPV_nolen(field_indent));
1777 7           sv_catpvf(out, "%s: %s", kjs, SvPV_nolen(ev));
1778 7           SvREFCNT_dec(ev);
1779             }
1780             } else {
1781 0           sv_catpvn(out, "\n", 1);
1782 0           sv_catpv(out, SvPV_nolen(item_indent));
1783 0           sv_catpvn(out, "-", 1);
1784             }
1785 6           SvREFCNT_dec((SV*)keys);
1786             }
1787             } else {
1788 3 100         for (I32 i = 0; i < n; i++) {
1789 2           SV **it = av_fetch(arr, i, 0);
1790 2           SV *ev = line_encode_value(ctx, *it);
1791 2 50         if (SvOK(ev) && SvCUR(ev) > 0) {
    50          
1792 2           sv_catpvn(out, "\n", 1);
1793 2           sv_catpv(out, SvPV_nolen(item_indent));
1794 2           sv_catpvf(out, "- %s", SvPV_nolen(ev));
1795             }
1796 2           SvREFCNT_dec(ev);
1797             }
1798             }
1799              
1800 6           SvREFCNT_dec(item_indent);
1801 6           SvREFCNT_dec(field_indent);
1802 6           ctx->depth--;
1803 6           return out;
1804             }
1805              
1806 55           static SV *line_encode_hash(line_encode_ctx_t *ctx, HV *hv) {
1807 55 100         if (ctx->depth >= ctx->max_depth) {
1808 2           croak("Maximum nesting depth exceeded (max: %ld)", (long)ctx->max_depth);
1809             }
1810 53           SV *out = newSVpvn("", 0);
1811 53           AV *keys = line_sorted_keys(hv, ctx);
1812 53           I32 nk = av_len(keys) + 1;
1813 53           int first = 1;
1814 53           SV *indent = newSVpvn("", 0);
1815 219 100         for (I32 i = 0; i < ctx->depth * ctx->indent; i++) sv_catpvn(indent, " ", 1);
1816              
1817 118 100         for (I32 i = 0; i < nk; i++) {
1818 77           SV **k = av_fetch(keys, i, 0);
1819 77           STRLEN kn; const char *ks = SvPV(*k, kn);
1820 77           SV **v = hv_fetch(hv, ks, (I32)kn, 0);
1821 91 50         if (!v) continue;
1822 77           SV *val = *v;
1823              
1824             SV *line;
1825 77 100         if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
    100          
1826 18           line = line_encode_object_with_array(ctx, SvPV_nolen(indent), *k, (AV*)SvRV(val));
1827 59 100         } else if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) {
    50          
1828 26           line = newSVpvf("%s%s:", SvPV_nolen(indent), ks);
1829 26           line_append_line(out, line, &first);
1830 26           SvREFCNT_dec(line);
1831 26           ctx->depth++;
1832 26           SV *nested = line_encode_hash(ctx, (HV*)SvRV(val));
1833 14           ctx->depth--;
1834 14           line_append_line(out, nested, &first);
1835 14           SvREFCNT_dec(nested);
1836 14           continue;
1837             } else {
1838 33           SV *pv = line_encode_primitive(val, ctx->delimiter);
1839 33           line = newSVpvf("%s%s: %s", SvPV_nolen(indent), ks, SvPV_nolen(pv));
1840 33           SvREFCNT_dec(pv);
1841             }
1842 51           line_append_line(out, line, &first);
1843 51           SvREFCNT_dec(line);
1844             }
1845              
1846 41           SvREFCNT_dec(indent);
1847 41           SvREFCNT_dec((SV*)keys);
1848 41           return out;
1849             }
1850              
1851 50           static SV *line_encode_value(line_encode_ctx_t *ctx, SV *value) {
1852 50 50         if (!SvOK(value)) return newSV(0);
1853 50 100         if (SvROK(value)) {
1854 31           SV *rv = SvRV(value);
1855             char addr[32];
1856 31           snprintf(addr, sizeof(addr), "%p", (void*)rv);
1857 31 50         if (hv_exists(ctx->seen, addr, (I32)strlen(addr))) {
1858 0           croak("Circular reference detected");
1859             }
1860 31           hv_store(ctx->seen, addr, (I32)strlen(addr), newSViv(1), 0);
1861 31 50         if (ctx->depth > ctx->max_depth) {
1862 0           croak("Maximum nesting depth exceeded (max: %ld)", (long)ctx->max_depth);
1863             }
1864 33 100         if (SvTYPE(rv) == SVt_PVHV) return line_encode_hash(ctx, (HV*)rv);
1865 2 50         if (SvTYPE(rv) == SVt_PVAV) return line_encode_array(ctx, (AV*)rv);
1866             }
1867 19           return line_encode_primitive(value, ctx->delimiter);
1868             }
1869              
1870 35           static void line_parse_encode_opts(SV *opts_sv, line_encode_ctx_t *ctx) {
1871 35           ctx->indent = 2;
1872 35           ctx->delimiter = ',';
1873 35           ctx->depth = 0;
1874 35           ctx->max_depth = 100;
1875 35           ctx->seen = newHV();
1876 35           ctx->priority = NULL;
1877              
1878 35 50         if (!opts_sv || !SvOK(opts_sv) || !SvROK(opts_sv) || SvTYPE(SvRV(opts_sv)) != SVt_PVHV) return;
    50          
    50          
    50          
1879 35           HV *hv = (HV*)SvRV(opts_sv);
1880             SV **v;
1881 35 100         v = hv_fetch(hv, "indent", 6, 0); if (v) ctx->indent = SvIV(*v);
1882 35 100         v = hv_fetch(hv, "max_depth", 9, 0); if (v) ctx->max_depth = SvIV(*v);
1883 35           v = hv_fetch(hv, "delimiter", 9, 0);
1884 35 100         if (v) {
1885 4           STRLEN n; const char *s = SvPV(*v, n);
1886 4 50         if (n > 0) ctx->delimiter = s[0];
1887             }
1888 35           v = hv_fetch(hv, "column_priority", 15, 0);
1889 35 100         if (v && SvROK(*v) && SvTYPE(SvRV(*v)) == SVt_PVAV) {
    50          
    50          
1890 8           AV *arr = (AV*)SvRV(*v);
1891 8           ctx->priority = newHV();
1892 8           I32 n = av_len(arr) + 1;
1893 24 100         for (I32 i = 0; i < n; i++) {
1894 16           SV **col = av_fetch(arr, i, 0);
1895 16 50         if (!col) continue;
1896 16           STRLEN cn; const char *cs = SvPV(*col, cn);
1897 16           hv_store(ctx->priority, cs, (I32)cn, newSViv(i), 0);
1898             }
1899             }
1900             }
1901              
1902 35           static SV *xs_encode_line_impl(SV *data_sv, SV *opts_sv) {
1903             line_encode_ctx_t ctx;
1904 35           line_parse_encode_opts(opts_sv, &ctx);
1905 35           SV *out = line_encode_value(&ctx, data_sv);
1906 33 50         if (!SvOK(out)) out = newSVpv("", 0);
1907 33           SvREFCNT_dec((SV*)ctx.seen);
1908 33 100         if (ctx.priority) SvREFCNT_dec((SV*)ctx.priority);
1909 33           return out;
1910             }
1911              
1912 3           static SV *xs_validate_line_impl(SV *text_sv, SV *opts_sv) {
1913 3           int ok = 1;
1914 3           ENTER;
1915 3           SAVETMPS;
1916 3           dSP;
1917 3 50         PUSHMARK(SP);
1918 3 50         XPUSHs(text_sv);
1919 3 50         if (opts_sv && SvOK(opts_sv)) XPUSHs(opts_sv);
    50          
    50          
1920 3           PUTBACK;
1921 3           call_pv("TOON::XS::_xs_decode_line", G_EVAL | G_DISCARD);
1922 3           SPAGAIN;
1923 3 50         if (SvTRUE(ERRSV)) ok = 0;
    50          
1924 3           PUTBACK;
1925 3 50         FREETMPS;
1926 3           LEAVE;
1927 3           return newSViv(ok ? 1 : 0);
1928             }
1929              
1930             MODULE = TOON::XS PACKAGE = TOON::XS
1931              
1932             void
1933             _xs_backend_init()
1934             CODE:
1935             /* no-op: ensures XS backend is loadable */
1936              
1937             SV *
1938             _xs_decode_brace(text)
1939             SV *text
1940             CODE:
1941 7           RETVAL = xs_decode_brace_impl(text);
1942             OUTPUT:
1943             RETVAL
1944              
1945             SV *
1946             _xs_encode_brace(data, opts = &PL_sv_undef)
1947             SV *data
1948             SV *opts
1949             CODE:
1950 5           RETVAL = xs_encode_brace_impl(data, opts);
1951             OUTPUT:
1952             RETVAL
1953              
1954             SV *
1955             _xs_decode_line(text, opts = &PL_sv_undef)
1956             SV *text
1957             SV *opts
1958             CODE:
1959 53           RETVAL = xs_decode_line_impl(text, opts);
1960             OUTPUT:
1961             RETVAL
1962              
1963             SV *
1964             _xs_encode_line(data, opts = &PL_sv_undef)
1965             SV *data
1966             SV *opts
1967             CODE:
1968 35           RETVAL = xs_encode_line_impl(data, opts);
1969             OUTPUT:
1970             RETVAL
1971              
1972             SV *
1973             _xs_validate_line(text, opts = &PL_sv_undef)
1974             SV *text
1975             SV *opts
1976             CODE:
1977 3           RETVAL = xs_validate_line_impl(text, opts);
1978             OUTPUT:
1979             RETVAL