File Coverage

lib/JSON/JQ.xs
Criterion Covered Total %
statement 134 184 72.8
branch 121 288 42.0
condition n/a
subroutine n/a
pod n/a
total 255 472 54.0


line stmt bran cond sub pod time code
1             /***************************************************************************
2             copyright : (C) 2021 - 2021 by Dongxu Ma
3             email : dongxu@cpan.org
4            
5             This library is free software; you can redistribute it and/or modify
6             it under MIT license. Refer to LICENSE within the package root folder
7             for full copyright.
8            
9             ***************************************************************************/
10            
11             #define PERL_NO_GET_CONTEXT
12             #include "EXTERN.h"
13             #include "perl.h"
14             #include "XSUB.h"
15            
16             #include "ppport.h"
17            
18             #include
19            
20             #include "jq.h"
21             #include "jv.h"
22             // TODO: get version from Alien::LibJQ cflags
23             #define JQ_VERSION "1.6"
24            
25             // utility functions for type marshaling
26             // they are not XS code
27 542           jv my_jv_input(pTHX_ void * arg) {
28 542 50         if (arg == NULL) {
29 0           return jv_null();
30             }
31             SV * const p_sv = arg;
32 542 50         SvGETMAGIC(p_sv);
33 542 100         if (SvTYPE(p_sv) == SVt_NULL || (SvTYPE(p_sv) < SVt_PVAV && !SvOK(p_sv))) {
    50          
    100          
    50          
    50          
34             // undef or JSON::null()
35 8           return jv_null();
36             }
37 534 100         else if (SvROK(p_sv) && SvTYPE(SvRV(p_sv)) == SVt_IV) {
    100          
38             // boolean: \0 or \1, equilvalent of $JSON::PP::true, $JSON::PP::false
39             //fprintf(stderr, "got boolean value: %s\n", SvTRUE(SvRV(p_sv)) ? "True" : "False");
40 2 50         return jv_bool((bool)SvTRUE(SvRV(p_sv)));
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    100          
    50          
    0          
    100          
    0          
41             }
42 532 100         else if (SvROK(p_sv) && sv_derived_from(p_sv, "JSON::PP::Boolean")) {
    100          
43             // boolean: $JSON::PP::true and $JSON::PP::false
44 15 50         return jv_bool((bool)SvTRUE(SvRV(p_sv)));
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    100          
    50          
    0          
    100          
    0          
45             }
46 517 100         else if (SvIOK(p_sv)) {
47             // integer, use perl's 'native size', see https://perldoc.perl.org/perlguts#What-is-an-%22IV%22?
48 33 50         return jv_number(SvIV(p_sv));
49             }
50 484 50         else if (SvUOK(p_sv)) {
51             // unsigned int
52 0 0         return jv_number(SvUV(p_sv));
53             }
54 484 100         else if (SvNOK(p_sv)) {
55             // double
56 6 50         return jv_number(SvNV(p_sv));
57             }
58 478 100         else if (SvPOK(p_sv)) {
59             // string
60             STRLEN len;
61 313 50         char * p_pv = SvUTF8(p_sv) ? SvPVutf8(p_sv, len) : SvPV(p_sv, len);
    0          
    50          
62             //fprintf(stderr, "my_jv_input() got string: %s\n", p_pv);
63 313           return jv_string_sized(p_pv, len);
64             }
65 165 50         else if (SvROK(p_sv) && SvTYPE(SvRV(p_sv)) == SVt_PVAV) {
    100          
66             // array
67 76           jv jval = jv_array();
68 76           AV * p_av = (AV *)SvRV(p_sv);
69 76           SSize_t len = av_len(p_av);
70 76 100         if (len < 0) {
71 1           return jval;
72             }
73             SSize_t i;
74 290 100         for (i = 0; i <= len; i++) {
75 215           jval = jv_array_append(jval, my_jv_input(aTHX_ *av_fetch(p_av, i, 0)));
76             }
77 76           return jval;
78             }
79 89 50         else if (SvROK(p_sv) && SvTYPE(SvRV(p_sv)) == SVt_PVHV) {
    50          
80             // hash
81 89           jv jval = jv_object();
82 89           HV * p_hv = (HV *)SvRV(p_sv);
83 89           I32 len = hv_iterinit(p_hv);
84             I32 i;
85 152 100         for (i = 0; i < len; i++) {
86 63           char * key = NULL;
87 63           I32 klen = 0;
88 63           SV * val = hv_iternextsv(p_hv, &key, &klen);
89 63           jval = jv_object_set(jval, jv_string_sized(key, klen), my_jv_input(aTHX_ val));
90             }
91 89           return jval;
92             }
93             else {
94             // not supported
95 0           croak("cannot convert perl object to json format: SvTYPE == %i", SvTYPE(p_sv));
96             }
97             // NOREACH
98             }
99            
100 135           void * my_jv_output(pTHX_ jv jval) {
101 135           jv_kind kind = jv_get_kind(jval);
102 135 100         if (kind == JV_KIND_NULL) {
103             // null
104 6           return newSV(0);
105             }
106 129 100         else if (kind == JV_KIND_FALSE) {
107             // boolean: false
108             // NOTE: get_sv("JSON::PP::false") doesn't work
109 4           SV * sv_false = newSV(0);
110             //fprintf(stderr, "set boolean: False\n");
111 4           return sv_setref_iv(sv_false, "JSON::PP::Boolean", 0);
112             }
113 125 100         else if (kind == JV_KIND_TRUE) {
114             // boolean: true
115 7           SV * sv_true = newSV(0);
116             //fprintf(stderr, "set boolean: True\n");
117 7           return sv_setref_iv(sv_true, "JSON::PP::Boolean", 1);
118             }
119 118 100         else if (kind == JV_KIND_NUMBER) {
120             // number
121 39           double val = jv_number_value(jval);
122 39           SV * p_sv = newSV(0);
123 39 100         if (jv_is_integer(jval)) {
124 34           sv_setiv(p_sv, val);
125             }
126             else {
127 5           sv_setnv(p_sv, val);
128             }
129             return p_sv;
130             }
131 79 100         else if (kind == JV_KIND_STRING) {
132             // string
133             //fprintf(stderr, "my_jv_output() got string: %s\n", jv_string_value(jval));
134             //return newSVpvn(jv_string_value(jval), jv_string_length_bytes(jval));
135             // NOTE: this might introduce unicode bug..
136 44           return newSVpvf("%s", jv_string_value(jval));
137             }
138 35 100         else if (kind == JV_KIND_ARRAY) {
139             // array
140 13           AV * p_av = newAV();
141 13           SSize_t len = (SSize_t)jv_array_length(jv_copy(jval));
142 13           av_extend(p_av, len - 1);
143             SSize_t i;
144 41 100         for (i = 0; i < len; i++) {
145 28           jv val = jv_array_get(jv_copy(jval), i);
146 28           av_push(p_av, (SV *)my_jv_output(aTHX_ val));
147 28           jv_free(val);
148             }
149 13           return newRV_noinc((SV *)p_av);
150             }
151 22 50         else if (kind == JV_KIND_OBJECT) {
152             // hash
153 22           HV * p_hv = newHV();
154 22           int iter = jv_object_iter(jval);
155 59 100         while (jv_object_iter_valid(jval, iter)) {
156 37           jv key = jv_object_iter_key(jval, iter);
157 37           jv val = jv_object_iter_value(jval, iter);
158 37 50         if (jv_get_kind(key) != JV_KIND_STRING) {
159 0           croak("cannot take non-string type as hash key: JV_KIND == %i", jv_get_kind(key));
160             }
161 37           const char * k = jv_string_value(key);
162 37           int klen = jv_string_length_bytes(key);
163 37           SV * v = (SV *)my_jv_output(aTHX_ val);
164 37           hv_store(p_hv, k, klen, v, 0);
165 37           jv_free(key);
166 37           jv_free(val);
167 37           iter = jv_object_iter_next(jval, iter);
168             }
169 22           return newRV_noinc((SV *)p_hv);
170             }
171             else {
172 0           croak("un-supported jv object type: JV_KIND == %i", kind);
173             }
174             // NOREACH
175             }
176            
177 2           static void my_error_cb(void * errors, jv jerr) {
178             dTHX;
179             // original jerr will be freed by jq engine
180 2           jerr = jv_copy(jerr);
181 2           av_push((AV *)errors, newSVpvn(jv_string_value(jerr), jv_string_length_bytes(jerr)));
182 2           }
183            
184 0           static void my_debug_cb(void * data, jv input) {
185             dTHX;
186 0           int dumpopts = *(int *)data;
187 0           jv_dumpf(JV_ARRAY(jv_string("DEBUG:"), input), stderr, dumpopts);
188 0           fprintf(stderr, "\n");
189 0           }
190            
191 162           static inline void assert_isa(pTHX_ SV * self) {
192 162 50         if (!sv_isa(self, "JSON::JQ")) {
193 0           croak("self is not a JSON::JQ object");
194             }
195 162           }
196            
197             // copied from main.c
198 0           static const char *skip_shebang(const char *p) {
199 0 0         if (strncmp(p, "#!", sizeof("#!") - 1) != 0)
200             return p;
201 0           const char *n = strchr(p, '\n');
202 0 0         if (n == NULL || n[1] != '#')
    0          
203             return p;
204 0           n = strchr(n + 1, '\n');
205 0 0         if (n == NULL || n[1] == '#' || n[1] == '\0' || n[-1] != '\\' || n[-2] == '\\')
    0          
    0          
    0          
    0          
206             return p;
207 0           n = strchr(n + 1, '\n');
208 0 0         if (n == NULL)
209             return p;
210 0           return n+1;
211             }
212            
213             MODULE = JSON::JQ PACKAGE = JSON::JQ
214            
215             PROTOTYPES: DISABLE
216            
217             int
218             JV_PRINT_INDENT_FLAGS(n)
219             int n
220             CODE:
221 51 50         RETVAL = JV_PRINT_INDENT_FLAGS(n);
    50          
222             OUTPUT:
223             RETVAL
224            
225             void
226             _init(self)
227             HV * self
228             INIT:
229             jq_state * _jq = NULL;
230             SV * sv_jq;
231             HV * hv_attr;
232             char * script;
233             AV * av_err;
234             int compiled = 0;
235             CODE:
236 51           assert_isa(aTHX_ ST(0));
237             // step 1. initialize
238 51           _jq = jq_init();
239 51 50         if (_jq == NULL) {
240 0           croak("cannot malloc jq engine");
241             }
242             else {
243 51           sv_jq = newSV(0);
244 51           sv_setiv(sv_jq, PTR2IV(_jq));
245 51           SvREADONLY_on(sv_jq);
246 51           hv_stores(self, "_jq", sv_jq);
247             }
248             // step 2. set error and debug callbacks
249 51           av_err = (AV *)SvRV(*hv_fetchs(self, "_errors", 0));
250 51           jq_set_error_cb(_jq, my_error_cb, av_err);
251 51 50         int dumpopts = (int)SvIV(*hv_fetchs(self, "_dumpopts", 0));
252 51           jq_set_debug_cb(_jq, my_debug_cb, &dumpopts);
253             // step 3. set initial attributes
254 51           hv_attr = (HV *)SvRV(*hv_fetchs(self, "_attribute", 0));
255 51           I32 len = hv_iterinit(hv_attr);
256             I32 i;
257 204 100         for (i = 0; i < len; i++) {
258 153           char * key = NULL;
259 153           I32 klen = 0;
260 153           SV * val = hv_iternextsv(hv_attr, &key, &klen);
261 153           jq_set_attr(_jq, jv_string_sized(key, klen), my_jv_input(aTHX_ val));
262             }
263             // set JQ_VERSION
264 51           jq_set_attr(_jq, jv_string("VERSION_DIR"), jv_string(JQ_VERSION));
265             // step 4. compile
266 51           jv args = my_jv_input(aTHX_ *hv_fetchs(self, "variable", 0));
267 51 50         if (hv_exists(self, "script_file", 11)) {
268 0 0         jv data = jv_load_file(SvPV_nolen(*hv_fetchs(self, "script_file", 0)), 1);
269 0 0         if (!jv_is_valid(data)) {
270 0           data = jv_invalid_get_msg(data);
271 0           my_error_cb(av_err, data);
272 0           jv_free(data);
273 0           XSRETURN_NO;
274             }
275 0           compiled = jq_compile_args(_jq, skip_shebang(jv_string_value(data)), args);
276 0           jv_free(data);
277             }
278             else {
279 51 50         script = SvPV_nolen(*hv_fetchs(self, "script", 0));
280 51           compiled = jq_compile_args(_jq, script, args);
281            
282             }
283 51 100         if (compiled) {
284 50 50         if (SvTRUE(get_sv("JSON::JQ::DUMP_DISASM", 0))) {
    50          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    0          
    50          
285 0           jq_dump_disassembly(_jq, 0);
286             printf("\n");
287             }
288 50           XSRETURN_YES;
289             }
290             else {
291             // args freed by jq engine
292             //jv_free(args);
293             // jq_teardown(&_jq); // no need to call destructor here, DESTROY will do
294 1           XSRETURN_NO;
295             }
296            
297             int
298             _process(self, sv_input, av_output)
299             HV * self
300             SV * sv_input
301             AV * av_output
302             INIT:
303             jq_state * _jq = NULL;
304             SV * sv_jq;
305             AV * av_err;
306             CODE:
307 60           assert_isa(aTHX_ ST(0));
308 60           sv_jq = *hv_fetchs(self, "_jq", 0);
309 60 50         _jq = INT2PTR(jq_state *, SvIV(sv_jq));
310 60           jv jv_input = my_jv_input(aTHX_ sv_input);
311 60 50         int jq_flags = (int)SvIV(*hv_fetchs(self, "jq_flags", 0));
312             // logic from process() in main.c
313 60           jq_start(_jq, jv_input, jq_flags);
314             jv result;
315             // clear previous call errors
316 60           av_err = (AV *)SvRV(*hv_fetchs(self, "_errors", 0));
317 60           av_clear(av_err);
318             int ret = 14;
319 320 100         while (jv_is_valid(result = jq_next(_jq))) {
320 70           av_push(av_output, (SV *)my_jv_output(aTHX_ result));
321 130 50         if (jv_get_kind(result) == JV_KIND_FALSE || jv_get_kind(result) == JV_KIND_NULL) {
    100          
322             ret = 11;
323             }
324             else {
325             ret = 0;
326             }
327             //jv_free(result);
328             }
329 60 50         if (jq_halted(_jq)) {
330             // jq program invoked `halt` or `halt_error`
331 0           jv exit_code = jq_get_exit_code(_jq);
332 0 0         if (!jv_get_kind(exit_code)) {
333             ret = 0;
334             }
335 0 0         else if (jv_get_kind(exit_code) == JV_KIND_NUMBER) {
336 0           ret = jv_number_value(exit_code);
337             }
338             else {
339             ret = 5;
340             }
341 0           jv_free(exit_code);
342 0           jv error_message = jq_get_error_message(_jq);
343 0 0         if (jv_get_kind(error_message) == JV_KIND_STRING) {
344 0           my_error_cb(av_err, error_message);
345             }
346 0 0         else if (jv_get_kind(error_message) == JV_KIND_NULL) {
347             // halt with no output
348             }
349 0 0         else if (jv_is_valid(error_message)) {
350 0           error_message = jv_dump_string(jv_copy(error_message), 0);
351 0           my_error_cb(av_err, error_message);
352             }
353             else {
354             // no message; use --debug-trace to see a message
355             }
356 0           jv_free(error_message);
357             }
358 60 50         else if (jv_invalid_has_msg(jv_copy(result))) {
359             // uncaught jq exception
360 0           jv msg = jv_invalid_get_msg(jv_copy(result));
361             //jv input_pos = jq_util_input_get_position(_jq);
362 0 0         if (jv_get_kind(msg) == JV_KIND_STRING) {
363             //av_push(av_err, newSVpvf("jq: error (at %s): %s", jv_string_value(input_pos), jv_string_value(msg)));
364 0           av_push(av_err, newSVpvf("jq: error: %s", jv_string_value(msg)));
365             }
366             else {
367 0           msg = jv_dump_string(msg, 0);
368             //av_push(av_err, newSVpvf("jq: error (at %s) (not a string): %s", jv_string_value(input_pos), jv_string_value(msg)));
369 0           av_push(av_err, newSVpvf("jq: error (not a string): %s", jv_string_value(msg)));
370             }
371             ret = 5;
372             //jv_free(input_pos);
373 0           jv_free(msg);
374             }
375 60           jv_free(result);
376             RETVAL = ret;
377             OUTPUT:
378             RETVAL
379            
380             void
381             DESTROY(self)
382             HV * self
383             INIT:
384 51           jq_state * _jq = NULL;
385             SV * sv_jq;
386             CODE:
387 51           assert_isa(aTHX_ ST(0));
388 51           sv_jq = *hv_fetchs(self, "_jq", 0);
389 51 50         _jq = INT2PTR(jq_state *, SvIV(sv_jq));
390 51 50         if (_jq != NULL) {
391 51 50         if (SvTRUE(get_sv("JSON::JQ::DEBUG", 0))) {
    50          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    0          
    50          
392 0           fprintf(stderr, "destroying jq object: %p\n", _jq);
393             }
394 51           jq_teardown(&_jq);
395             }