| /* vi:set ts=8 sts=4 sw=4 noet: |
| * |
| * VIM - Vi IMproved by Bram Moolenaar |
| * |
| * Do ":help uganda" in Vim to read copying and usage conditions. |
| * Do ":help credits" in Vim to see a list of people who contributed. |
| * See README.txt for an overview of the Vim source code. |
| */ |
| |
| /* |
| * blob.c: Blob support by Yasuhiro Matsumoto |
| */ |
| |
| #include "vim.h" |
| |
| #if defined(FEAT_EVAL) || defined(PROTO) |
| |
| /* |
| * Allocate an empty blob. |
| * Caller should take care of the reference count. |
| */ |
| blob_T * |
| blob_alloc(void) |
| { |
| blob_T *blob = ALLOC_CLEAR_ONE(blob_T); |
| |
| if (blob != NULL) |
| ga_init2(&blob->bv_ga, 1, 100); |
| return blob; |
| } |
| |
| /* |
| * Allocate an empty blob for a return value, with reference count set. |
| * Returns OK or FAIL. |
| */ |
| int |
| rettv_blob_alloc(typval_T *rettv) |
| { |
| blob_T *b = blob_alloc(); |
| |
| if (b == NULL) |
| return FAIL; |
| |
| rettv_blob_set(rettv, b); |
| return OK; |
| } |
| |
| /* |
| * Set a blob as the return value. |
| */ |
| void |
| rettv_blob_set(typval_T *rettv, blob_T *b) |
| { |
| rettv->v_type = VAR_BLOB; |
| rettv->vval.v_blob = b; |
| if (b != NULL) |
| ++b->bv_refcount; |
| } |
| |
| int |
| blob_copy(blob_T *from, typval_T *to) |
| { |
| int ret = OK; |
| |
| to->v_type = VAR_BLOB; |
| to->v_lock = 0; |
| if (from == NULL) |
| to->vval.v_blob = NULL; |
| else if (rettv_blob_alloc(to) == FAIL) |
| ret = FAIL; |
| else |
| { |
| int len = from->bv_ga.ga_len; |
| |
| if (len > 0) |
| { |
| to->vval.v_blob->bv_ga.ga_data = |
| vim_memsave(from->bv_ga.ga_data, len); |
| if (to->vval.v_blob->bv_ga.ga_data == NULL) |
| len = 0; |
| } |
| to->vval.v_blob->bv_ga.ga_len = len; |
| to->vval.v_blob->bv_ga.ga_maxlen = len; |
| } |
| return ret; |
| } |
| |
| void |
| blob_free(blob_T *b) |
| { |
| ga_clear(&b->bv_ga); |
| vim_free(b); |
| } |
| |
| /* |
| * Unreference a blob: decrement the reference count and free it when it |
| * becomes zero. |
| */ |
| void |
| blob_unref(blob_T *b) |
| { |
| if (b != NULL && --b->bv_refcount <= 0) |
| blob_free(b); |
| } |
| |
| /* |
| * Get the length of data. |
| */ |
| long |
| blob_len(blob_T *b) |
| { |
| if (b == NULL) |
| return 0L; |
| return b->bv_ga.ga_len; |
| } |
| |
| /* |
| * Get byte "idx" in blob "b". |
| * Caller must check that "idx" is valid. |
| */ |
| int |
| blob_get(blob_T *b, int idx) |
| { |
| return ((char_u*)b->bv_ga.ga_data)[idx]; |
| } |
| |
| /* |
| * Store one byte "c" in blob "b" at "idx". |
| * Caller must make sure that "idx" is valid. |
| */ |
| void |
| blob_set(blob_T *b, int idx, char_u c) |
| { |
| ((char_u*)b->bv_ga.ga_data)[idx] = c; |
| } |
| |
| /* |
| * Return TRUE when two blobs have exactly the same values. |
| */ |
| int |
| blob_equal( |
| blob_T *b1, |
| blob_T *b2) |
| { |
| int i; |
| int len1 = blob_len(b1); |
| int len2 = blob_len(b2); |
| |
| // empty and NULL are considered the same |
| if (len1 == 0 && len2 == 0) |
| return TRUE; |
| if (b1 == b2) |
| return TRUE; |
| if (len1 != len2) |
| return FALSE; |
| |
| for (i = 0; i < b1->bv_ga.ga_len; i++) |
| if (blob_get(b1, i) != blob_get(b2, i)) return FALSE; |
| return TRUE; |
| } |
| |
| /* |
| * Read "blob" from file "fd". |
| * Return OK or FAIL. |
| */ |
| int |
| read_blob(FILE *fd, blob_T *blob) |
| { |
| struct stat st; |
| |
| if (fstat(fileno(fd), &st) < 0) |
| return FAIL; |
| if (ga_grow(&blob->bv_ga, st.st_size) == FAIL) |
| return FAIL; |
| blob->bv_ga.ga_len = st.st_size; |
| if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd) |
| < (size_t)blob->bv_ga.ga_len) |
| return FAIL; |
| return OK; |
| } |
| |
| /* |
| * Write "blob" to file "fd". |
| * Return OK or FAIL. |
| */ |
| int |
| write_blob(FILE *fd, blob_T *blob) |
| { |
| if (fwrite(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd) |
| < (size_t)blob->bv_ga.ga_len) |
| { |
| emsg(_(e_write)); |
| return FAIL; |
| } |
| return OK; |
| } |
| |
| /* |
| * Convert a blob to a readable form: "0z00112233.44556677.8899" |
| */ |
| char_u * |
| blob2string(blob_T *blob, char_u **tofree, char_u *numbuf) |
| { |
| int i; |
| garray_T ga; |
| |
| if (blob == NULL) |
| { |
| *tofree = NULL; |
| return (char_u *)"0z"; |
| } |
| |
| // Store bytes in the growarray. |
| ga_init2(&ga, 1, 4000); |
| ga_concat(&ga, (char_u *)"0z"); |
| for (i = 0; i < blob_len(blob); i++) |
| { |
| if (i > 0 && (i & 3) == 0) |
| ga_concat(&ga, (char_u *)"."); |
| vim_snprintf((char *)numbuf, NUMBUFLEN, "%02X", (int)blob_get(blob, i)); |
| ga_concat(&ga, numbuf); |
| } |
| *tofree = ga.ga_data; |
| return *tofree; |
| } |
| |
| /* |
| * Convert a string variable, in the format of blob2string(), to a blob. |
| * Return NULL when conversion failed. |
| */ |
| blob_T * |
| string2blob(char_u *str) |
| { |
| blob_T *blob = blob_alloc(); |
| char_u *s = str; |
| |
| if (blob == NULL) |
| return NULL; |
| if (s[0] != '0' || (s[1] != 'z' && s[1] != 'Z')) |
| goto failed; |
| s += 2; |
| while (vim_isxdigit(*s)) |
| { |
| if (!vim_isxdigit(s[1])) |
| goto failed; |
| ga_append(&blob->bv_ga, (hex2nr(s[0]) << 4) + hex2nr(s[1])); |
| s += 2; |
| if (*s == '.' && vim_isxdigit(s[1])) |
| ++s; |
| } |
| if (*skipwhite(s) != NUL) |
| goto failed; // text after final digit |
| |
| ++blob->bv_refcount; |
| return blob; |
| |
| failed: |
| blob_free(blob); |
| return NULL; |
| } |
| |
| int |
| blob_slice_or_index( |
| blob_T *blob, |
| int is_range, |
| varnumber_T n1, |
| varnumber_T n2, |
| int exclusive, |
| typval_T *rettv) |
| { |
| long len = blob_len(blob); |
| |
| if (is_range) |
| { |
| // The resulting variable is a sub-blob. If the indexes |
| // are out of range the result is empty. |
| if (n1 < 0) |
| { |
| n1 = len + n1; |
| if (n1 < 0) |
| n1 = 0; |
| } |
| if (n2 < 0) |
| n2 = len + n2; |
| else if (n2 >= len) |
| n2 = len - (exclusive ? 0 : 1); |
| if (exclusive) |
| --n2; |
| if (n1 >= len || n2 < 0 || n1 > n2) |
| { |
| clear_tv(rettv); |
| rettv->v_type = VAR_BLOB; |
| rettv->vval.v_blob = NULL; |
| } |
| else |
| { |
| blob_T *new_blob = blob_alloc(); |
| long i; |
| |
| if (new_blob != NULL) |
| { |
| if (ga_grow(&new_blob->bv_ga, n2 - n1 + 1) == FAIL) |
| { |
| blob_free(new_blob); |
| return FAIL; |
| } |
| new_blob->bv_ga.ga_len = n2 - n1 + 1; |
| for (i = n1; i <= n2; i++) |
| blob_set(new_blob, i - n1, blob_get(blob, i)); |
| |
| clear_tv(rettv); |
| rettv_blob_set(rettv, new_blob); |
| } |
| } |
| } |
| else |
| { |
| // The resulting variable is a byte value. |
| // If the index is too big or negative that is an error. |
| if (n1 < 0) |
| n1 = len + n1; |
| if (n1 < len && n1 >= 0) |
| { |
| int v = blob_get(blob, n1); |
| |
| clear_tv(rettv); |
| rettv->v_type = VAR_NUMBER; |
| rettv->vval.v_number = v; |
| } |
| else |
| { |
| semsg(_(e_blobidx), n1); |
| return FAIL; |
| } |
| } |
| return OK; |
| } |
| |
| /* |
| * Check if "n1"- is a valid index for a blobl with length "bloblen". |
| */ |
| int |
| check_blob_index(long bloblen, varnumber_T n1, int quiet) |
| { |
| if (n1 < 0 || n1 > bloblen) |
| { |
| if (!quiet) |
| semsg(_(e_blobidx), n1); |
| return FAIL; |
| } |
| return OK; |
| } |
| |
| /* |
| * Check if "n1"-"n2" is a valid range for a blob with length "bloblen". |
| */ |
| int |
| check_blob_range(long bloblen, varnumber_T n1, varnumber_T n2, int quiet) |
| { |
| if (n2 < 0 || n2 >= bloblen || n2 < n1) |
| { |
| if (!quiet) |
| semsg(_(e_blobidx), n2); |
| return FAIL; |
| } |
| return OK; |
| } |
| |
| /* |
| * Set bytes "n1" to "n2" (inclusive) in "dest" to the value of "src". |
| * Caller must make sure "src" is a blob. |
| * Returns FAIL if the number of bytes does not match. |
| */ |
| int |
| blob_set_range(blob_T *dest, long n1, long n2, typval_T *src) |
| { |
| int il, ir; |
| |
| if (n2 - n1 + 1 != blob_len(src->vval.v_blob)) |
| { |
| emsg(_("E972: Blob value does not have the right number of bytes")); |
| return FAIL; |
| } |
| |
| ir = 0; |
| for (il = n1; il <= n2; il++) |
| blob_set(dest, il, blob_get(src->vval.v_blob, ir++)); |
| return OK; |
| } |
| |
| /* |
| * "remove({blob})" function |
| */ |
| void |
| blob_remove(typval_T *argvars, typval_T *rettv) |
| { |
| int error = FALSE; |
| long idx = (long)tv_get_number_chk(&argvars[1], &error); |
| long end; |
| |
| if (!error) |
| { |
| blob_T *b = argvars[0].vval.v_blob; |
| int len = blob_len(b); |
| char_u *p; |
| |
| if (idx < 0) |
| // count from the end |
| idx = len + idx; |
| if (idx < 0 || idx >= len) |
| { |
| semsg(_(e_blobidx), idx); |
| return; |
| } |
| if (argvars[2].v_type == VAR_UNKNOWN) |
| { |
| // Remove one item, return its value. |
| p = (char_u *)b->bv_ga.ga_data; |
| rettv->vval.v_number = (varnumber_T) *(p + idx); |
| mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1); |
| --b->bv_ga.ga_len; |
| } |
| else |
| { |
| blob_T *blob; |
| |
| // Remove range of items, return list with values. |
| end = (long)tv_get_number_chk(&argvars[2], &error); |
| if (error) |
| return; |
| if (end < 0) |
| // count from the end |
| end = len + end; |
| if (end >= len || idx > end) |
| { |
| semsg(_(e_blobidx), end); |
| return; |
| } |
| blob = blob_alloc(); |
| if (blob == NULL) |
| return; |
| blob->bv_ga.ga_len = end - idx + 1; |
| if (ga_grow(&blob->bv_ga, end - idx + 1) == FAIL) |
| { |
| vim_free(blob); |
| return; |
| } |
| p = (char_u *)b->bv_ga.ga_data; |
| mch_memmove((char_u *)blob->bv_ga.ga_data, p + idx, |
| (size_t)(end - idx + 1)); |
| ++blob->bv_refcount; |
| rettv->v_type = VAR_BLOB; |
| rettv->vval.v_blob = blob; |
| |
| mch_memmove(p + idx, p + end + 1, (size_t)(len - end)); |
| b->bv_ga.ga_len -= end - idx + 1; |
| } |
| } |
| } |
| |
| #endif // defined(FEAT_EVAL) |