/*************************************************************************** copyright : (C) 2021 - 2021 by Dongxu Ma email : dongxu@cpan.org This library is free software; you can redistribute it and/or modify it under MIT license. Refer to LICENSE within the package root folder for full copyright. ***************************************************************************/ #define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" #include #include "jq.h" #include "jv.h" // TODO: get version from Alien::LibJQ cflags #define JQ_VERSION "1.6" // utility functions for type marshaling // they are not XS code jv my_jv_input(pTHX_ void * arg) { if (arg == NULL) { return jv_null(); } SV * const p_sv = arg; SvGETMAGIC(p_sv); if (SvTYPE(p_sv) == SVt_NULL || (SvTYPE(p_sv) < SVt_PVAV && !SvOK(p_sv))) { // undef or JSON::null() return jv_null(); } else if (SvROK(p_sv) && SvTYPE(SvRV(p_sv)) == SVt_IV) { // boolean: \0 or \1, equilvalent of $JSON::PP::true, $JSON::PP::false //fprintf(stderr, "got boolean value: %s\n", SvTRUE(SvRV(p_sv)) ? "True" : "False"); return jv_bool((bool)SvTRUE(SvRV(p_sv))); } else if (SvROK(p_sv) && sv_derived_from(p_sv, "JSON::PP::Boolean")) { // boolean: $JSON::PP::true and $JSON::PP::false return jv_bool((bool)SvTRUE(SvRV(p_sv))); } else if (SvIOK(p_sv)) { // integer, use perl's 'native size', see https://perldoc.perl.org/perlguts#What-is-an-%22IV%22? return jv_number(SvIV(p_sv)); } else if (SvUOK(p_sv)) { // unsigned int return jv_number(SvUV(p_sv)); } else if (SvNOK(p_sv)) { // double return jv_number(SvNV(p_sv)); } else if (SvPOK(p_sv)) { // string STRLEN len; char * p_pv = SvUTF8(p_sv) ? SvPVutf8(p_sv, len) : SvPV(p_sv, len); //fprintf(stderr, "my_jv_input() got string: %s\n", p_pv); return jv_string_sized(p_pv, len); } else if (SvROK(p_sv) && SvTYPE(SvRV(p_sv)) == SVt_PVAV) { // array jv jval = jv_array(); AV * p_av = (AV *)SvRV(p_sv); SSize_t len = av_len(p_av); if (len < 0) { return jval; } SSize_t i; for (i = 0; i <= len; i++) { jval = jv_array_append(jval, my_jv_input(aTHX_ *av_fetch(p_av, i, 0))); } return jval; } else if (SvROK(p_sv) && SvTYPE(SvRV(p_sv)) == SVt_PVHV) { // hash jv jval = jv_object(); HV * p_hv = (HV *)SvRV(p_sv); I32 len = hv_iterinit(p_hv); I32 i; for (i = 0; i < len; i++) { char * key = NULL; I32 klen = 0; SV * val = hv_iternextsv(p_hv, &key, &klen); jval = jv_object_set(jval, jv_string_sized(key, klen), my_jv_input(aTHX_ val)); } return jval; } else { // not supported croak("cannot convert perl object to json format: SvTYPE == %i", SvTYPE(p_sv)); } // NOREACH } void * my_jv_output(pTHX_ jv jval) { jv_kind kind = jv_get_kind(jval); if (kind == JV_KIND_NULL) { // null return newSV(0); } else if (kind == JV_KIND_FALSE) { // boolean: false // NOTE: get_sv("JSON::PP::false") doesn't work SV * sv_false = newSV(0); //fprintf(stderr, "set boolean: False\n"); return sv_setref_iv(sv_false, "JSON::PP::Boolean", 0); } else if (kind == JV_KIND_TRUE) { // boolean: true SV * sv_true = newSV(0); //fprintf(stderr, "set boolean: True\n"); return sv_setref_iv(sv_true, "JSON::PP::Boolean", 1); } else if (kind == JV_KIND_NUMBER) { // number double val = jv_number_value(jval); SV * p_sv = newSV(0); if (jv_is_integer(jval)) { sv_setiv(p_sv, val); } else { sv_setnv(p_sv, val); } return p_sv; } else if (kind == JV_KIND_STRING) { // string //fprintf(stderr, "my_jv_output() got string: %s\n", jv_string_value(jval)); //return newSVpvn(jv_string_value(jval), jv_string_length_bytes(jval)); // NOTE: this might introduce unicode bug.. return newSVpvf("%s", jv_string_value(jval)); } else if (kind == JV_KIND_ARRAY) { // array AV * p_av = newAV(); SSize_t len = (SSize_t)jv_array_length(jv_copy(jval)); av_extend(p_av, len - 1); SSize_t i; for (i = 0; i < len; i++) { jv val = jv_array_get(jv_copy(jval), i); av_push(p_av, (SV *)my_jv_output(aTHX_ val)); jv_free(val); } return newRV_noinc((SV *)p_av); } else if (kind == JV_KIND_OBJECT) { // hash HV * p_hv = newHV(); int iter = jv_object_iter(jval); while (jv_object_iter_valid(jval, iter)) { jv key = jv_object_iter_key(jval, iter); jv val = jv_object_iter_value(jval, iter); if (jv_get_kind(key) != JV_KIND_STRING) { croak("cannot take non-string type as hash key: JV_KIND == %i", jv_get_kind(key)); } const char * k = jv_string_value(key); int klen = jv_string_length_bytes(key); SV * v = (SV *)my_jv_output(aTHX_ val); hv_store(p_hv, k, klen, v, 0); jv_free(key); jv_free(val); iter = jv_object_iter_next(jval, iter); } return newRV_noinc((SV *)p_hv); } else { croak("un-supported jv object type: JV_KIND == %i", kind); } // NOREACH } static void my_error_cb(void * errors, jv jerr) { dTHX; // original jerr will be freed by jq engine jerr = jv_copy(jerr); av_push((AV *)errors, newSVpvn(jv_string_value(jerr), jv_string_length_bytes(jerr))); } static void my_debug_cb(void * data, jv input) { dTHX; int dumpopts = *(int *)data; jv_dumpf(JV_ARRAY(jv_string("DEBUG:"), input), stderr, dumpopts); fprintf(stderr, "\n"); } static inline void assert_isa(pTHX_ SV * self) { if (!sv_isa(self, "JSON::JQ")) { croak("self is not a JSON::JQ object"); } } // copied from main.c static const char *skip_shebang(const char *p) { if (strncmp(p, "#!", sizeof("#!") - 1) != 0) return p; const char *n = strchr(p, '\n'); if (n == NULL || n[1] != '#') return p; n = strchr(n + 1, '\n'); if (n == NULL || n[1] == '#' || n[1] == '\0' || n[-1] != '\\' || n[-2] == '\\') return p; n = strchr(n + 1, '\n'); if (n == NULL) return p; return n+1; } MODULE = JSON::JQ PACKAGE = JSON::JQ PROTOTYPES: DISABLE int JV_PRINT_INDENT_FLAGS(n) int n CODE: RETVAL = JV_PRINT_INDENT_FLAGS(n); OUTPUT: RETVAL void _init(self) HV * self INIT: jq_state * _jq = NULL; SV * sv_jq; HV * hv_attr; char * script; AV * av_err; int compiled = 0; CODE: assert_isa(aTHX_ ST(0)); // step 1. initialize _jq = jq_init(); if (_jq == NULL) { croak("cannot malloc jq engine"); } else { sv_jq = newSV(0); sv_setiv(sv_jq, PTR2IV(_jq)); SvREADONLY_on(sv_jq); hv_stores(self, "_jq", sv_jq); } // step 2. set error and debug callbacks av_err = (AV *)SvRV(*hv_fetchs(self, "_errors", 0)); jq_set_error_cb(_jq, my_error_cb, av_err); int dumpopts = (int)SvIV(*hv_fetchs(self, "_dumpopts", 0)); jq_set_debug_cb(_jq, my_debug_cb, &dumpopts); // step 3. set initial attributes hv_attr = (HV *)SvRV(*hv_fetchs(self, "_attribute", 0)); I32 len = hv_iterinit(hv_attr); I32 i; for (i = 0; i < len; i++) { char * key = NULL; I32 klen = 0; SV * val = hv_iternextsv(hv_attr, &key, &klen); jq_set_attr(_jq, jv_string_sized(key, klen), my_jv_input(aTHX_ val)); } // set JQ_VERSION jq_set_attr(_jq, jv_string("VERSION_DIR"), jv_string(JQ_VERSION)); // step 4. compile jv args = my_jv_input(aTHX_ *hv_fetchs(self, "variable", 0)); if (hv_exists(self, "script_file", 11)) { jv data = jv_load_file(SvPV_nolen(*hv_fetchs(self, "script_file", 0)), 1); if (!jv_is_valid(data)) { data = jv_invalid_get_msg(data); my_error_cb(av_err, data); jv_free(data); XSRETURN_NO; } compiled = jq_compile_args(_jq, skip_shebang(jv_string_value(data)), args); jv_free(data); } else { script = SvPV_nolen(*hv_fetchs(self, "script", 0)); compiled = jq_compile_args(_jq, script, args); } if (compiled) { if (SvTRUE(get_sv("JSON::JQ::DUMP_DISASM", 0))) { jq_dump_disassembly(_jq, 0); printf("\n"); } XSRETURN_YES; } else { // args freed by jq engine //jv_free(args); // jq_teardown(&_jq); // no need to call destructor here, DESTROY will do XSRETURN_NO; } int _process(self, sv_input, av_output) HV * self SV * sv_input AV * av_output INIT: jq_state * _jq = NULL; SV * sv_jq; AV * av_err; CODE: assert_isa(aTHX_ ST(0)); sv_jq = *hv_fetchs(self, "_jq", 0); _jq = INT2PTR(jq_state *, SvIV(sv_jq)); jv jv_input = my_jv_input(aTHX_ sv_input); int jq_flags = (int)SvIV(*hv_fetchs(self, "jq_flags", 0)); // logic from process() in main.c jq_start(_jq, jv_input, jq_flags); jv result; // clear previous call errors av_err = (AV *)SvRV(*hv_fetchs(self, "_errors", 0)); av_clear(av_err); int ret = 14; while (jv_is_valid(result = jq_next(_jq))) { av_push(av_output, (SV *)my_jv_output(aTHX_ result)); if (jv_get_kind(result) == JV_KIND_FALSE || jv_get_kind(result) == JV_KIND_NULL) { ret = 11; } else { ret = 0; } //jv_free(result); } if (jq_halted(_jq)) { // jq program invoked `halt` or `halt_error` jv exit_code = jq_get_exit_code(_jq); if (!jv_get_kind(exit_code)) { ret = 0; } else if (jv_get_kind(exit_code) == JV_KIND_NUMBER) { ret = jv_number_value(exit_code); } else { ret = 5; } jv_free(exit_code); jv error_message = jq_get_error_message(_jq); if (jv_get_kind(error_message) == JV_KIND_STRING) { my_error_cb(av_err, error_message); } else if (jv_get_kind(error_message) == JV_KIND_NULL) { // halt with no output } else if (jv_is_valid(error_message)) { error_message = jv_dump_string(jv_copy(error_message), 0); my_error_cb(av_err, error_message); } else { // no message; use --debug-trace to see a message } jv_free(error_message); } else if (jv_invalid_has_msg(jv_copy(result))) { // uncaught jq exception jv msg = jv_invalid_get_msg(jv_copy(result)); //jv input_pos = jq_util_input_get_position(_jq); if (jv_get_kind(msg) == JV_KIND_STRING) { //av_push(av_err, newSVpvf("jq: error (at %s): %s", jv_string_value(input_pos), jv_string_value(msg))); av_push(av_err, newSVpvf("jq: error: %s", jv_string_value(msg))); } else { msg = jv_dump_string(msg, 0); //av_push(av_err, newSVpvf("jq: error (at %s) (not a string): %s", jv_string_value(input_pos), jv_string_value(msg))); av_push(av_err, newSVpvf("jq: error (not a string): %s", jv_string_value(msg))); } ret = 5; //jv_free(input_pos); jv_free(msg); } jv_free(result); RETVAL = ret; OUTPUT: RETVAL void DESTROY(self) HV * self INIT: jq_state * _jq = NULL; SV * sv_jq; CODE: assert_isa(aTHX_ ST(0)); sv_jq = *hv_fetchs(self, "_jq", 0); _jq = INT2PTR(jq_state *, SvIV(sv_jq)); if (_jq != NULL) { if (SvTRUE(get_sv("JSON::JQ::DEBUG", 0))) { fprintf(stderr, "destroying jq object: %p\n", _jq); } jq_teardown(&_jq); }