#include #include #include #include #include #include "jv_alloc.h" #include "jv_private.h" // making this static verbose function here // until we introduce a less confusing naming scheme // of jv_* API with regards to the memory management static double jv_number_get_value_and_consume(jv number) { double value = jv_number_value(number); jv_free(number); return value; } static jv parse_slice(jv j, jv slice, int* pstart, int* pend) { // Array slices jv start_jv = jv_object_get(jv_copy(slice), jv_string("start")); jv end_jv = jv_object_get(slice, jv_string("end")); if (jv_get_kind(start_jv) == JV_KIND_NULL) { jv_free(start_jv); start_jv = jv_number(0); } int len; if (jv_get_kind(j) == JV_KIND_ARRAY) { len = jv_array_length(j); } else if (jv_get_kind(j) == JV_KIND_STRING) { len = jv_string_length_codepoints(j); } else { /* * XXX This should be dead code because callers shouldn't call this * function if `j' is neither an array nor a string. */ jv_free(j); jv_free(start_jv); jv_free(end_jv); return jv_invalid_with_msg(jv_string("Only arrays and strings can be sliced")); } if (jv_get_kind(end_jv) == JV_KIND_NULL) { jv_free(end_jv); end_jv = jv_number(len); } if (jv_get_kind(start_jv) != JV_KIND_NUMBER || jv_get_kind(end_jv) != JV_KIND_NUMBER) { jv_free(start_jv); jv_free(end_jv); return jv_invalid_with_msg(jv_string("Array/string slice indices must be integers")); } double dstart = jv_number_value(start_jv); double dend = jv_number_value(end_jv); int start, end; jv_free(start_jv); jv_free(end_jv); if (isnan(dstart)) dstart = 0; if (dstart < 0) dstart += len; if (dstart < 0) dstart = 0; if (dstart > len) dstart = len; start = dstart > INT_MAX ? INT_MAX : (int)dstart; // Rounds down if (isnan(dend)) dend = len; if (dend < 0) dend += len; if (dend < 0) dend = start; end = dend > INT_MAX ? INT_MAX : (int)dend; if (end > len) end = len; if (end < len) end += end < dend ? 1 : 0; // We round start down // but round end up if (end < start) end = start; assert(0 <= start && start <= end && end <= len); *pstart = start; *pend = end; return jv_true(); } jv jv_get(jv t, jv k) { jv v; if (jv_get_kind(t) == JV_KIND_OBJECT && jv_get_kind(k) == JV_KIND_STRING) { v = jv_object_get(t, k); if (!jv_is_valid(v)) { jv_free(v); v = jv_null(); } } else if (jv_get_kind(t) == JV_KIND_ARRAY && jv_get_kind(k) == JV_KIND_NUMBER) { if (jvp_number_is_nan(k)) { jv_free(t); v = jv_null(); } else { double didx = jv_number_value(k); if (jvp_number_is_nan(k)) { v = jv_null(); } else { if (didx < INT_MIN) didx = INT_MIN; if (didx > INT_MAX) didx = INT_MAX; int idx = (int)didx; if (idx < 0) idx += jv_array_length(jv_copy(t)); v = jv_array_get(t, idx); if (!jv_is_valid(v)) { jv_free(v); v = jv_null(); } } } jv_free(k); } else if (jv_get_kind(t) == JV_KIND_ARRAY && jv_get_kind(k) == JV_KIND_OBJECT) { int start, end; jv e = parse_slice(jv_copy(t), k, &start, &end); if (jv_get_kind(e) == JV_KIND_TRUE) { v = jv_array_slice(t, start, end); } else { jv_free(t); v = e; } } else if (jv_get_kind(t) == JV_KIND_STRING && jv_get_kind(k) == JV_KIND_OBJECT) { int start, end; jv e = parse_slice(jv_copy(t), k, &start, &end); if (jv_get_kind(e) == JV_KIND_TRUE) { v = jv_string_slice(t, start, end); } else { jv_free(t); v = e; } } else if (jv_get_kind(t) == JV_KIND_ARRAY && jv_get_kind(k) == JV_KIND_ARRAY) { v = jv_array_indexes(t, k); } else if (jv_get_kind(t) == JV_KIND_NULL && (jv_get_kind(k) == JV_KIND_STRING || jv_get_kind(k) == JV_KIND_NUMBER || jv_get_kind(k) == JV_KIND_OBJECT)) { jv_free(t); jv_free(k); v = jv_null(); } else { /* * If k is a short string it's probably from a jq .foo expression or * similar, in which case putting it in the invalid msg may help the * user. The length 30 is arbitrary. */ if (jv_get_kind(k) == JV_KIND_STRING && jv_string_length_bytes(jv_copy(k)) < 30) { v = jv_invalid_with_msg(jv_string_fmt("Cannot index %s with string \"%s\"", jv_kind_name(jv_get_kind(t)), jv_string_value(k))); } else { v = jv_invalid_with_msg(jv_string_fmt("Cannot index %s with %s", jv_kind_name(jv_get_kind(t)), jv_kind_name(jv_get_kind(k)))); } jv_free(t); jv_free(k); } return v; } jv jv_set(jv t, jv k, jv v) { if (!jv_is_valid(v)) { jv_free(t); jv_free(k); return v; } int isnull = jv_get_kind(t) == JV_KIND_NULL; if (jv_get_kind(k) == JV_KIND_STRING && (jv_get_kind(t) == JV_KIND_OBJECT || isnull)) { if (isnull) t = jv_object(); t = jv_object_set(t, k, v); } else if (jv_get_kind(k) == JV_KIND_NUMBER && (jv_get_kind(t) == JV_KIND_ARRAY || isnull)) { if (jvp_number_is_nan(k)) { jv_free(t); jv_free(k); t = jv_invalid_with_msg(jv_string("Cannot set array element at NaN index")); } else { double didx = jv_number_value(k); if (didx < INT_MIN) didx = INT_MIN; if (didx > INT_MAX) didx = INT_MAX; if (isnull) t = jv_array(); t = jv_array_set(t, (int)didx, v); jv_free(k); } } else if (jv_get_kind(k) == JV_KIND_OBJECT && (jv_get_kind(t) == JV_KIND_ARRAY || isnull)) { if (isnull) t = jv_array(); int start, end; jv e = parse_slice(jv_copy(t), k, &start, &end); if (jv_get_kind(e) == JV_KIND_TRUE) { if (jv_get_kind(v) == JV_KIND_ARRAY) { int array_len = jv_array_length(jv_copy(t)); assert(0 <= start && start <= end && end <= array_len); int slice_len = end - start; int insert_len = jv_array_length(jv_copy(v)); if (slice_len < insert_len) { // array is growing int shift = insert_len - slice_len; for (int i = array_len - 1; i >= end; i--) { t = jv_array_set(t, i + shift, jv_array_get(jv_copy(t), i)); } } else if (slice_len > insert_len) { // array is shrinking int shift = slice_len - insert_len; for (int i = end; i < array_len; i++) { t = jv_array_set(t, i - shift, jv_array_get(jv_copy(t), i)); } t = jv_array_slice(t, 0, array_len - shift); } for (int i=0; i < insert_len; i++) { t = jv_array_set(t, start + i, jv_array_get(jv_copy(v), i)); } jv_free(v); } else { jv_free(t); jv_free(v); t = jv_invalid_with_msg(jv_string_fmt("A slice of an array can only be assigned another array")); } } else { jv_free(t); jv_free(v); t = e; } } else if (jv_get_kind(k) == JV_KIND_OBJECT && jv_get_kind(t) == JV_KIND_STRING) { jv_free(t); jv_free(k); jv_free(v); /* Well, why not? We should implement this... */ t = jv_invalid_with_msg(jv_string_fmt("Cannot update string slices")); } else { jv err = jv_invalid_with_msg(jv_string_fmt("Cannot update field at %s index of %s", jv_kind_name(jv_get_kind(k)), jv_kind_name(jv_get_kind(t)))); jv_free(t); jv_free(k); jv_free(v); t = err; } return t; } jv jv_has(jv t, jv k) { assert(jv_is_valid(t)); assert(jv_is_valid(k)); jv ret; if (jv_get_kind(t) == JV_KIND_NULL) { jv_free(t); jv_free(k); ret = jv_false(); } else if (jv_get_kind(t) == JV_KIND_OBJECT && jv_get_kind(k) == JV_KIND_STRING) { jv elem = jv_object_get(t, k); ret = jv_bool(jv_is_valid(elem)); jv_free(elem); } else if (jv_get_kind(t) == JV_KIND_ARRAY && jv_get_kind(k) == JV_KIND_NUMBER) { if (jvp_number_is_nan(k)) { jv_free(t); ret = jv_false(); } else { jv elem = jv_array_get(t, (int)jv_number_value(k)); ret = jv_bool(jv_is_valid(elem)); jv_free(elem); } jv_free(k); } else { ret = jv_invalid_with_msg(jv_string_fmt("Cannot check whether %s has a %s key", jv_kind_name(jv_get_kind(t)), jv_kind_name(jv_get_kind(k)))); jv_free(t); jv_free(k); } return ret; } // assumes keys is a sorted array static jv jv_dels(jv t, jv keys) { assert(jv_get_kind(keys) == JV_KIND_ARRAY); assert(jv_is_valid(t)); if (jv_get_kind(t) == JV_KIND_NULL || jv_array_length(jv_copy(keys)) == 0) { // no change } else if (jv_get_kind(t) == JV_KIND_ARRAY) { // extract slices, they must be handled differently jv neg_keys = jv_array(); jv nonneg_keys = jv_array(); jv new_array = jv_array(); jv starts = jv_array(), ends = jv_array(); jv_array_foreach(keys, i, key) { if (jv_get_kind(key) == JV_KIND_NUMBER) { if (jv_number_value(key) < 0) { neg_keys = jv_array_append(neg_keys, key); } else { nonneg_keys = jv_array_append(nonneg_keys, key); } } else if (jv_get_kind(key) == JV_KIND_OBJECT) { int start, end; jv e = parse_slice(jv_copy(t), key, &start, &end); if (jv_get_kind(e) == JV_KIND_TRUE) { starts = jv_array_append(starts, jv_number(start)); ends = jv_array_append(ends, jv_number(end)); } else { jv_free(new_array); jv_free(key); new_array = e; goto arr_out; } } else { jv_free(new_array); new_array = jv_invalid_with_msg(jv_string_fmt("Cannot delete %s element of array", jv_kind_name(jv_get_kind(key)))); jv_free(key); goto arr_out; } } int neg_idx = 0; int nonneg_idx = 0; int len = jv_array_length(jv_copy(t)); jv_array_foreach(t, i, elem) { int del = 0; while (neg_idx < jv_array_length(jv_copy(neg_keys))) { int delidx = len + (int)jv_number_get_value_and_consume(jv_array_get(jv_copy(neg_keys), neg_idx)); if (i == delidx) { del = 1; } if (i < delidx) { break; } neg_idx++; } while (nonneg_idx < jv_array_length(jv_copy(nonneg_keys))) { int delidx = (int)jv_number_get_value_and_consume(jv_array_get(jv_copy(nonneg_keys), nonneg_idx)); if (i == delidx) { del = 1; } if (i < delidx) { break; } nonneg_idx++; } for (int sidx=0; !del && sidx start); int delkey = jv_array_length(jv_array_get(jv_copy(paths), i)) == start + 1; jv key = jv_array_get(jv_array_get(jv_copy(paths), i), start); while (j < jv_array_length(jv_copy(paths)) && jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start))) j++; // if i <= entry < j, then entry starts with key if (delkey) { // deleting this entire key, we don't care about any more specific deletions delkeys = jv_array_append(delkeys, key); } else { // deleting certain sub-parts of this key jv subobject = jv_get(jv_copy(object), jv_copy(key)); if (!jv_is_valid(subobject)) { jv_free(key); jv_free(object); object = subobject; break; } else if (jv_get_kind(subobject) == JV_KIND_NULL) { jv_free(key); jv_free(subobject); } else { jv newsubobject = delpaths_sorted(subobject, jv_array_slice(jv_copy(paths), i, j), start+1); if (!jv_is_valid(newsubobject)) { jv_free(key); jv_free(object); object = newsubobject; break; } object = jv_set(object, key, newsubobject); } if (!jv_is_valid(object)) break; } i = j; } jv_free(paths); if (jv_is_valid(object)) object = jv_dels(object, delkeys); else jv_free(delkeys); return object; } jv jv_delpaths(jv object, jv paths) { if (jv_get_kind(paths) != JV_KIND_ARRAY) { jv_free(object); jv_free(paths); return jv_invalid_with_msg(jv_string("Paths must be specified as an array")); } paths = jv_sort(paths, jv_copy(paths)); jv_array_foreach(paths, i, elem) { if (jv_get_kind(elem) != JV_KIND_ARRAY) { jv_free(object); jv_free(paths); jv err = jv_invalid_with_msg(jv_string_fmt("Path must be specified as array, not %s", jv_kind_name(jv_get_kind(elem)))); jv_free(elem); return err; } jv_free(elem); } if (jv_array_length(jv_copy(paths)) == 0) { // nothing is being deleted jv_free(paths); return object; } if (jv_array_length(jv_array_get(jv_copy(paths), 0)) == 0) { // everything is being deleted jv_free(paths); jv_free(object); return jv_null(); } return delpaths_sorted(object, paths, 0); } static int string_cmp(const void* pa, const void* pb){ const jv* a = pa; const jv* b = pb; int lena = jv_string_length_bytes(jv_copy(*a)); int lenb = jv_string_length_bytes(jv_copy(*b)); int minlen = lena < lenb ? lena : lenb; int r = memcmp(jv_string_value(*a), jv_string_value(*b), minlen); if (r == 0) r = lena - lenb; return r; } jv jv_keys_unsorted(jv x) { if (jv_get_kind(x) != JV_KIND_OBJECT) return jv_keys(x); jv answer = jv_array_sized(jv_object_length(jv_copy(x))); jv_object_foreach(x, key, value) { answer = jv_array_append(answer, key); jv_free(value); } jv_free(x); return answer; } jv jv_keys(jv x) { if (jv_get_kind(x) == JV_KIND_OBJECT) { int nkeys = jv_object_length(jv_copy(x)); jv* keys = jv_mem_calloc(nkeys, sizeof(jv)); int kidx = 0; jv_object_foreach(x, key, value) { keys[kidx++] = key; jv_free(value); } qsort(keys, nkeys, sizeof(jv), string_cmp); jv answer = jv_array_sized(nkeys); for (int i = 0; i= jv_array_length(jv_copy(a)); int b_done = i >= jv_array_length(jv_copy(b)); if (a_done || b_done) { r = b_done - a_done; //suddenly, logic break; } jv xa = jv_array_get(jv_copy(a), i); jv xb = jv_array_get(jv_copy(b), i); r = jv_cmp(xa, xb); i++; } break; } case JV_KIND_OBJECT: { jv keys_a = jv_keys(jv_copy(a)); jv keys_b = jv_keys(jv_copy(b)); r = jv_cmp(jv_copy(keys_a), keys_b); if (r == 0) { jv_array_foreach(keys_a, i, key) { jv xa = jv_object_get(jv_copy(a), jv_copy(key)); jv xb = jv_object_get(jv_copy(b), key); r = jv_cmp(xa, xb); if (r) break; } } jv_free(keys_a); break; } } jv_free(a); jv_free(b); return r; } struct sort_entry { jv object; jv key; int index; }; static int sort_cmp(const void* pa, const void* pb) { const struct sort_entry* a = pa; const struct sort_entry* b = pb; int r = jv_cmp(jv_copy(a->key), jv_copy(b->key)); // comparing by index if r == 0 makes the sort stable return r ? r : (a->index - b->index); } static struct sort_entry* sort_items(jv objects, jv keys) { assert(jv_get_kind(objects) == JV_KIND_ARRAY); assert(jv_get_kind(keys) == JV_KIND_ARRAY); assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys))); int n = jv_array_length(jv_copy(objects)); struct sort_entry* entries = jv_mem_calloc(n, sizeof(struct sort_entry)); for (int i=0; i 0) { jv curr_key = entries[0].key; jv group = jv_array_append(jv_array(), entries[0].object); for (int i = 1; i < n; i++) { if (jv_equal(jv_copy(curr_key), jv_copy(entries[i].key))) { jv_free(entries[i].key); } else { jv_free(curr_key); curr_key = entries[i].key; ret = jv_array_append(ret, group); group = jv_array(); } group = jv_array_append(group, entries[i].object); } jv_free(curr_key); ret = jv_array_append(ret, group); } jv_mem_free(entries); return ret; }