vim/src/blob.c
Ernie Rael e79e207760
patch 9.1.0027: Vim is missing a foreach() func
Problem:  Vim is missing a foreach() func
Solution: Implement foreach({expr1}, {expr2}) function,
          which applies {expr2} for each item in {expr1}
          without changing it (Ernie Rael)

closes: #12166

Signed-off-by: Ernie Rael <errael@raelity.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2024-01-13 11:47:33 +01:00

863 lines
17 KiB
C

/* 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_ID(blob_T, aid_blob_alloc);
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 len;
to->v_type = VAR_BLOB;
to->v_lock = 0;
if (from == NULL)
{
to->vval.v_blob = NULL;
return OK;
}
if (rettv_blob_alloc(to) == FAIL)
return FAIL;
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 OK;
}
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 "byte" in blob "blob" at "idx".
* Caller must make sure that "idx" is valid.
*/
void
blob_set(blob_T *blob, int idx, int byte)
{
((char_u*)blob->bv_ga.ga_data)[idx] = byte;
}
/*
* Store one byte "byte" in blob "blob" at "idx".
* Append one byte if needed.
*/
void
blob_set_append(blob_T *blob, int idx, int byte)
{
garray_T *gap = &blob->bv_ga;
// Allow for appending a byte. Setting a byte beyond
// the end is an error otherwise.
if (idx < gap->ga_len
|| (idx == gap->ga_len && ga_grow(gap, 1) == OK))
{
blob_set(blob, idx, byte);
if (idx == gap->ga_len)
++gap->ga_len;
}
}
/*
* 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".
* Caller has allocated a blob in "rettv".
* Return OK or FAIL.
*/
int
read_blob(FILE *fd, typval_T *rettv, off_T offset, off_T size_arg)
{
blob_T *blob = rettv->vval.v_blob;
struct stat st;
int whence;
off_T size = size_arg;
if (fstat(fileno(fd), &st) < 0)
return FAIL; // can't read the file, error
if (offset >= 0)
{
// The size defaults to the whole file. If a size is given it is
// limited to not go past the end of the file.
if (size == -1 || (size > st.st_size - offset
#ifdef S_ISCHR
&& !S_ISCHR(st.st_mode)
#endif
))
// size may become negative, checked below
size = st.st_size - offset;
whence = SEEK_SET;
}
else
{
// limit the offset to not go before the start of the file
if (-offset > st.st_size
#ifdef S_ISCHR
&& !S_ISCHR(st.st_mode)
#endif
)
offset = -st.st_size;
// Size defaults to reading until the end of the file.
if (size == -1 || size > -offset)
size = -offset;
whence = SEEK_END;
}
if (size <= 0)
return OK;
if (offset != 0 && vim_fseek(fd, offset, whence) != 0)
return OK;
if (ga_grow(&blob->bv_ga, (int)size) == FAIL)
return FAIL;
blob->bv_ga.ga_len = (int)size;
if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len)
{
// An empty blob is returned on error.
blob_free(rettv->vval.v_blob);
rettv->vval.v_blob = NULL;
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_error_while_writing));
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", blob_get(blob, i));
ga_concat(&ga, numbuf);
}
ga_append(&ga, NUL); // append a NUL at the end
*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;
}
/*
* Returns a slice of 'blob' from index 'n1' to 'n2' in 'rettv'. The length of
* the blob is 'len'. Returns an empty blob if the indexes are out of range.
*/
static int
blob_slice(
blob_T *blob,
long len,
varnumber_T n1,
varnumber_T n2,
int exclusive,
typval_T *rettv)
{
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);
}
}
return OK;
}
/*
* Return the byte value in 'blob' at index 'idx' in 'rettv'. If the index is
* too big or negative that is an error. The length of the blob is 'len'.
*/
static int
blob_index(
blob_T *blob,
int len,
varnumber_T idx,
typval_T *rettv)
{
// The resulting variable is a byte value.
// If the index is too big or negative that is an error.
if (idx < 0)
idx = len + idx;
if (idx < len && idx >= 0)
{
int v = blob_get(blob, idx);
clear_tv(rettv);
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = v;
}
else
{
semsg(_(e_blob_index_out_of_range_nr), idx);
return FAIL;
}
return OK;
}
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)
return blob_slice(blob, len, n1, n2, exclusive, rettv);
else
return blob_index(blob, len, n1, rettv);
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_blob_index_out_of_range_nr), 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_blob_index_out_of_range_nr), 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(_(e_blob_value_does_not_have_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;
}
/*
* "add(blob, item)" function
*/
void
blob_add(typval_T *argvars, typval_T *rettv)
{
blob_T *b = argvars[0].vval.v_blob;
int error = FALSE;
varnumber_T n;
if (b == NULL)
{
if (in_vim9script())
emsg(_(e_cannot_add_to_null_blob));
return;
}
if (value_check_lock(b->bv_lock, (char_u *)N_("add() argument"), TRUE))
return;
n = tv_get_number_chk(&argvars[1], &error);
if (error)
return;
ga_append(&b->bv_ga, (int)n);
copy_tv(&argvars[0], rettv);
}
/*
* "remove({blob}, {idx} [, {end}])" function
*/
void
blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
{
blob_T *b = argvars[0].vval.v_blob;
blob_T *newblob;
int error = FALSE;
long idx;
long end;
int len;
char_u *p;
if (b != NULL && value_check_lock(b->bv_lock, arg_errmsg, TRUE))
return;
idx = (long)tv_get_number_chk(&argvars[1], &error);
if (error)
return;
len = blob_len(b);
if (idx < 0)
// count from the end
idx = len + idx;
if (idx < 0 || idx >= len)
{
semsg(_(e_blob_index_out_of_range_nr), 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;
return;
}
// Remove range of items, return blob 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_blob_index_out_of_range_nr), end);
return;
}
newblob = blob_alloc();
if (newblob == NULL)
return;
newblob->bv_ga.ga_len = end - idx + 1;
if (ga_grow(&newblob->bv_ga, end - idx + 1) == FAIL)
{
vim_free(newblob);
return;
}
p = (char_u *)b->bv_ga.ga_data;
mch_memmove((char_u *)newblob->bv_ga.ga_data, p + idx,
(size_t)(end - idx + 1));
++newblob->bv_refcount;
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = newblob;
if (len - end - 1 > 0)
mch_memmove(p + idx, p + end + 1, (size_t)(len - end - 1));
b->bv_ga.ga_len -= end - idx + 1;
}
/*
* Implementation of map() and filter() for a Blob. Apply "expr" to every
* number in Blob "blob_arg" and return the result in "rettv".
*/
void
blob_filter_map(
blob_T *blob_arg,
filtermap_T filtermap,
typval_T *expr,
char_u *arg_errmsg,
typval_T *rettv)
{
blob_T *b = blob_arg;
int i;
typval_T tv;
varnumber_T val;
blob_T *b_ret;
int idx = 0;
int rem;
typval_T newtv;
funccall_T *fc;
if (filtermap == FILTERMAP_MAPNEW)
{
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = NULL;
}
if (b == NULL || (filtermap == FILTERMAP_FILTER
&& value_check_lock(b->bv_lock, arg_errmsg, TRUE)))
return;
b_ret = b;
if (filtermap == FILTERMAP_MAPNEW)
{
if (blob_copy(b, rettv) == FAIL)
return;
b_ret = rettv->vval.v_blob;
}
// set_vim_var_nr() doesn't set the type
set_vim_var_type(VV_KEY, VAR_NUMBER);
int prev_lock = b->bv_lock;
if (b->bv_lock == 0)
b->bv_lock = VAR_LOCKED;
// Create one funccall_T for all eval_expr_typval() calls.
fc = eval_expr_get_funccal(expr, &newtv);
for (i = 0; i < b->bv_ga.ga_len; i++)
{
tv.v_type = VAR_NUMBER;
val = blob_get(b, i);
tv.vval.v_number = val;
set_vim_var_nr(VV_KEY, idx);
if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
|| did_emsg)
break;
if (filtermap != FILTERMAP_FOREACH)
{
if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
{
clear_tv(&newtv);
emsg(_(e_invalid_operation_for_blob));
break;
}
if (filtermap != FILTERMAP_FILTER)
{
if (newtv.vval.v_number != val)
blob_set(b_ret, i, newtv.vval.v_number);
}
else if (rem)
{
char_u *p = (char_u *)blob_arg->bv_ga.ga_data;
mch_memmove(p + i, p + i + 1,
(size_t)b->bv_ga.ga_len - i - 1);
--b->bv_ga.ga_len;
--i;
}
}
++idx;
}
b->bv_lock = prev_lock;
if (fc != NULL)
remove_funccal();
}
/*
* "insert(blob, {item} [, {idx}])" function
*/
void
blob_insert_func(typval_T *argvars, typval_T *rettv)
{
blob_T *b = argvars[0].vval.v_blob;
long before = 0;
int error = FALSE;
int val, len;
char_u *p;
if (b == NULL)
{
if (in_vim9script())
emsg(_(e_cannot_add_to_null_blob));
return;
}
if (value_check_lock(b->bv_lock, (char_u *)N_("insert() argument"), TRUE))
return;
len = blob_len(b);
if (argvars[2].v_type != VAR_UNKNOWN)
{
before = (long)tv_get_number_chk(&argvars[2], &error);
if (error)
return; // type error; errmsg already given
if (before < 0 || before > len)
{
semsg(_(e_invalid_argument_str), tv_get_string(&argvars[2]));
return;
}
}
val = tv_get_number_chk(&argvars[1], &error);
if (error)
return;
if (val < 0 || val > 255)
{
semsg(_(e_invalid_argument_str), tv_get_string(&argvars[1]));
return;
}
if (ga_grow(&b->bv_ga, 1) == FAIL)
return;
p = (char_u *)b->bv_ga.ga_data;
mch_memmove(p + before + 1, p + before, (size_t)len - before);
*(p + before) = val;
++b->bv_ga.ga_len;
copy_tv(&argvars[0], rettv);
}
/*
* Implementation of reduce() for Blob "argvars[0]" using the function "expr"
* starting with the optional initial value "argvars[2]" and return the result
* in "rettv".
*/
void
blob_reduce(
typval_T *argvars,
typval_T *expr,
typval_T *rettv)
{
blob_T *b = argvars[0].vval.v_blob;
int called_emsg_start = called_emsg;
int r;
typval_T initial;
typval_T argv[3];
int i;
if (argvars[2].v_type == VAR_UNKNOWN)
{
if (b == NULL || b->bv_ga.ga_len == 0)
{
semsg(_(e_reduce_of_an_empty_str_with_no_initial_value), "Blob");
return;
}
initial.v_type = VAR_NUMBER;
initial.vval.v_number = blob_get(b, 0);
i = 1;
}
else if (check_for_number_arg(argvars, 2) == FAIL)
return;
else
{
initial = argvars[2];
i = 0;
}
copy_tv(&initial, rettv);
if (b == NULL)
return;
for ( ; i < b->bv_ga.ga_len; i++)
{
argv[0] = *rettv;
argv[1].v_type = VAR_NUMBER;
argv[1].vval.v_number = blob_get(b, i);
r = eval_expr_typval(expr, TRUE, argv, 2, NULL, rettv);
clear_tv(&argv[0]);
if (r == FAIL || called_emsg != called_emsg_start)
return;
}
}
/*
* "reverse({blob})" function
*/
void
blob_reverse(blob_T *b, typval_T *rettv)
{
int i, len = blob_len(b);
for (i = 0; i < len / 2; i++)
{
int tmp = blob_get(b, i);
blob_set(b, i, blob_get(b, len - i - 1));
blob_set(b, len - i - 1, tmp);
}
rettv_blob_set(rettv, b);
}
/*
* blob2list() function
*/
void
f_blob2list(typval_T *argvars, typval_T *rettv)
{
blob_T *blob;
list_T *l;
int i;
if (rettv_list_alloc(rettv) == FAIL)
return;
if (check_for_blob_arg(argvars, 0) == FAIL)
return;
blob = argvars->vval.v_blob;
l = rettv->vval.v_list;
for (i = 0; i < blob_len(blob); i++)
list_append_number(l, blob_get(blob, i));
}
/*
* list2blob() function
*/
void
f_list2blob(typval_T *argvars, typval_T *rettv)
{
list_T *l;
listitem_T *li;
blob_T *blob;
if (rettv_blob_alloc(rettv) == FAIL)
return;
blob = rettv->vval.v_blob;
if (check_for_list_arg(argvars, 0) == FAIL)
return;
l = argvars->vval.v_list;
if (l == NULL)
return;
CHECK_LIST_MATERIALIZE(l);
FOR_ALL_LIST_ITEMS(l, li)
{
int error;
varnumber_T n;
error = FALSE;
n = tv_get_number_chk(&li->li_tv, &error);
if (error == TRUE || n < 0 || n > 255)
{
if (!error)
semsg(_(e_invalid_value_for_blob_nr), n);
ga_clear(&blob->bv_ga);
return;
}
ga_append(&blob->bv_ga, n);
}
}
#endif // defined(FEAT_EVAL)