# HG changeset patch # User Bram Moolenaar # Date 1618255804 -7200 # Node ID 602e528a8e43642c19a4f14c747555e42f9f825d # Parent a1e1e1eaa09bf2af8bb6004f7ec9a5ee3d3c6477 patch 8.2.2757: Vim9: blob tests for legacy and Vim9 script are separate Commit: https://github.com/vim/vim/commit/68452177ca4cda4a9d5f93892e437447cf9404c8 Author: Bram Moolenaar Date: Mon Apr 12 21:21:02 2021 +0200 patch 8.2.2757: Vim9: blob tests for legacy and Vim9 script are separate Problem: Vim9: blob tests for legacy and Vim9 script are separate. Solution: Add CheckLegacyAndVim9Success(). Make blob index assign work. diff --git a/src/blob.c b/src/blob.c --- a/src/blob.c +++ b/src/blob.c @@ -337,6 +337,28 @@ blob_slice_or_index( } /* + * 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 diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -399,3 +399,5 @@ EXTERN char e_variable_arguments_type_mu INIT(= N_("E1180: Variable arguments type must be a list: %s")); EXTERN char e_cannot_use_underscore_here[] INIT(= N_("E1181: Cannot use an underscore here")); +EXTERN char e_blob_required[] + INIT(= N_("E1182: Blob required")); diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1319,23 +1319,12 @@ set_var_lval( if (lp->ll_range && rettv->v_type == VAR_BLOB) { - int il, ir; - if (lp->ll_empty2) lp->ll_n2 = blob_len(lp->ll_blob) - 1; - if (lp->ll_n2 - lp->ll_n1 + 1 != blob_len(rettv->vval.v_blob)) - { - emsg(_("E972: Blob value does not have the right number of bytes")); + if (blob_set_range(lp->ll_blob, lp->ll_n1, lp->ll_n2, + rettv) == FAIL) return; - } - if (lp->ll_empty2) - lp->ll_n2 = blob_len(lp->ll_blob); - - ir = 0; - for (il = lp->ll_n1; il <= lp->ll_n2; il++) - blob_set(lp->ll_blob, il, - blob_get(rettv->vval.v_blob, ir++)); } else { diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -3429,22 +3429,25 @@ find_ex_command( // "varname.key" is an expression. || (*p == '.' && ASCII_ISALPHA(p[1])))) { - char_u *after = p; + char_u *after = eap->cmd; // When followed by "=" or "+=" then it is an assignment. + // Skip over the whole thing, it can be: + // name.member = val + // name[a : b] = val + // name[idx] = val + // name[idx].member = val + // etc. + eap->cmdidx = CMD_eval; ++emsg_silent; - if (*after == '.') - after = skipwhite(after + 1); if (skip_expr(&after, NULL) == OK) + { after = skipwhite(after); - else - after = (char_u *)""; - if (*after == '=' || (*after != NUL && after[1] == '=') + if (*after == '=' || (*after != NUL && after[1] == '=') || (after[0] == '.' && after[1] == '.' && after[2] == '=')) - eap->cmdidx = CMD_var; - else - eap->cmdidx = CMD_eval; + eap->cmdidx = CMD_var; + } --emsg_silent; return eap->cmd; } diff --git a/src/proto/blob.pro b/src/proto/blob.pro --- a/src/proto/blob.pro +++ b/src/proto/blob.pro @@ -14,5 +14,6 @@ int write_blob(FILE *fd, blob_T *blob); char_u *blob2string(blob_T *blob, char_u **tofree, char_u *numbuf); blob_T *string2blob(char_u *str); int blob_slice_or_index(blob_T *blob, int is_range, varnumber_T n1, varnumber_T n2, int exclusive, typval_T *rettv); +int blob_set_range(blob_T *dest, long n1, long n2, typval_T *src); void blob_remove(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */ diff --git a/src/testdir/test_blob.vim b/src/testdir/test_blob.vim --- a/src/testdir/test_blob.vim +++ b/src/testdir/test_blob.vim @@ -1,5 +1,7 @@ " Tests for the Blob types +source vim9.vim + func TearDown() " Run garbage collection after every test call test_garbagecollect_now() @@ -9,73 +11,81 @@ endfunc " Blob creation from constant func Test_blob_create() - let b = 0zDEADBEEF - call assert_equal(v:t_blob, type(b)) - call assert_equal(4, len(b)) - call assert_equal(0xDE, b[0]) - call assert_equal(0xAD, b[1]) - call assert_equal(0xBE, b[2]) - call assert_equal(0xEF, b[3]) - call assert_fails('let x = b[4]') + let lines =<< trim END + VAR b = 0zDEADBEEF + call assert_equal(v:t_blob, type(b)) + call assert_equal(4, len(b)) + call assert_equal(0xDE, b[0]) + call assert_equal(0xAD, b[1]) + call assert_equal(0xBE, b[2]) + call assert_equal(0xEF, b[3]) + call assert_fails('VAR x = b[4]') - call assert_equal(0xDE, get(b, 0)) - call assert_equal(0xEF, get(b, 3)) + call assert_equal(0xDE, get(b, 0)) + call assert_equal(0xEF, get(b, 3)) - call assert_fails('let b = 0z1', 'E973:') - call assert_fails('let b = 0z1x', 'E973:') - call assert_fails('let b = 0z12345', 'E973:') + call assert_fails('VAR b = 0z1', 'E973:') + call assert_fails('VAR b = 0z1x', 'E973:') + call assert_fails('VAR b = 0z12345', 'E973:') - call assert_equal(0z, test_null_blob()) + call assert_equal(0z, test_null_blob()) - let b = 0z001122.33445566.778899.aabbcc.dd - call assert_equal(0z00112233445566778899aabbccdd, b) - call assert_fails('let b = 0z1.1') - call assert_fails('let b = 0z.') - call assert_fails('let b = 0z001122.') - call assert_fails('call get("", 1)', 'E896:') - call assert_equal(0, len(test_null_blob())) + LET b = 0z001122.33445566.778899.aabbcc.dd + call assert_equal(0z00112233445566778899aabbccdd, b) + call assert_fails('VAR b = 0z1.1') + call assert_fails('VAR b = 0z.') + call assert_fails('VAR b = 0z001122.') + call assert_fails('call get("", 1)', 'E896:') + call assert_equal(0, len(test_null_blob())) + END + call CheckLegacyAndVim9Success(lines) endfunc " assignment to a blob func Test_blob_assign() - let b = 0zDEADBEEF - let b2 = b[1:2] - call assert_equal(0zADBE, b2) + let lines =<< trim END + VAR b = 0zDEADBEEF + VAR b2 = b[1 : 2] + call assert_equal(0zADBE, b2) - let bcopy = b[:] - call assert_equal(b, bcopy) - call assert_false(b is bcopy) + VAR bcopy = b[:] + call assert_equal(b, bcopy) + call assert_false(b is bcopy) + + LET b = 0zDEADBEEF + LET b2 = b + call assert_true(b is b2) + LET b[:] = 0z11223344 + call assert_equal(0z11223344, b) + call assert_equal(0z11223344, b2) + call assert_true(b is b2) - let b = 0zDEADBEEF - let b2 = b - call assert_true(b is b2) - let b[:] = 0z11223344 - call assert_equal(0z11223344, b) - call assert_equal(0z11223344, b2) - call assert_true(b is b2) + LET b = 0zDEADBEEF + LET b[3 :] = 0z66 + call assert_equal(0zDEADBE66, b) + LET b[: 1] = 0z8899 + call assert_equal(0z8899BE66, b) + LET b = 0zDEADBEEF + LET b += 0z99 + call assert_equal(0zDEADBEEF99, b) + + VAR l = [0z12] + VAR m = deepcopy(l) + LET m[0] = 0z34 #" E742 or E741 should not occur. + END + call CheckLegacyAndVim9Success(lines) + + " TODO: move to above once it works let b = 0zDEADBEEF - let b[3:] = 0z66 - call assert_equal(0zDEADBE66, b) - let b[:1] = 0z8899 - call assert_equal(0z8899BE66, b) - - call assert_fails('let b[2:3] = 0z112233', 'E972:') - call assert_fails('let b[2:3] = 0z11', 'E972:') - call assert_fails('let b[3:2] = 0z', 'E979:') + call assert_fails('let b[2 : 3] = 0z112233', 'E972:') + call assert_fails('let b[2 : 3] = 0z11', 'E972:') + call assert_fails('let b[3 : 2] = 0z', 'E979:') - let b = 0zDEADBEEF - let b += 0z99 - call assert_equal(0zDEADBEEF99, b) - - call assert_fails('let b .= 0z33', 'E734:') - call assert_fails('let b .= "xx"', 'E734:') + call assert_fails('let b ..= 0z33', 'E734:') + call assert_fails('let b ..= "xx"', 'E734:') call assert_fails('let b += "xx"', 'E734:') - call assert_fails('let b[1:1] .= 0z55', 'E734:') - - let l = [0z12] - let m = deepcopy(l) - let m[0] = 0z34 " E742 or E741 should not occur. + call assert_fails('let b[1 : 1] ..= 0z55', 'E734:') endfunc func Test_blob_get_range() diff --git a/src/testdir/vim9.vim b/src/testdir/vim9.vim --- a/src/testdir/vim9.vim +++ b/src/testdir/vim9.vim @@ -133,3 +133,38 @@ def CheckDefExecAndScriptFailure2( CheckDefExecFailure(lines, errorDef, lnum) CheckScriptFailure(['vim9script'] + lines, errorScript, lnum + 1) enddef + + +" Check that "lines" inside a legacy function has no error. +func CheckLegacySuccess(lines) + let cwd = getcwd() + let fname = 'XlegacySuccess' .. s:sequence + let s:sequence += 1 + call writefile(['func Func()'] + a:lines + ['endfunc'], fname) + try + exe 'so ' .. fname + call Func() + delfunc! Func + finally + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Execute "lines" in a legacy function, :def function and Vim9 script. +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +def CheckLegacyAndVim9Success(lines: list) + var legacylines = lines->mapnew((_, v) => + v->substitute('\', 'let', 'g') + ->substitute('\', 'let', 'g') + ->substitute('#"', ' "', 'g')) + CheckLegacySuccess(legacylines) + + var vim9lines = lines->mapnew((_, v) => + v->substitute('\', 'var', 'g') + ->substitute('\lhs_varlen; char_u *p; int r = OK; + int need_white_before = TRUE; + int empty_second; p = var_start + varlen; if (*p == '[') { p = skipwhite(p + 1); - r = compile_expr0(&p, cctx); + if (*p == ':') + { + // empty first index, push zero + r = generate_PUSHNR(cctx, 0); + need_white_before = FALSE; + } + else + r = compile_expr0(&p, cctx); if (r == OK && *skipwhite(p) == ':') { // unlet var[idx : idx] - if (is_assign) - { - semsg(_(e_cannot_use_range_with_assignment_str), p); - return FAIL; - } + // blob[idx : idx] = value *range = TRUE; p = skipwhite(p); - if (!IS_WHITE_OR_NUL(p[-1]) || !IS_WHITE_OR_NUL(p[1])) + empty_second = *skipwhite(p + 1) == ']'; + if ((need_white_before && !IS_WHITE_OR_NUL(p[-1])) + || (!empty_second && !IS_WHITE_OR_NUL(p[1]))) { semsg(_(e_white_space_required_before_and_after_str_at_str), ":", p); return FAIL; } p = skipwhite(p + 1); - r = compile_expr0(&p, cctx); + if (*p == ']') + // empty second index, push "none" + r = generate_PUSHSPEC(cctx, VVAL_NONE); + else + r = compile_expr0(&p, cctx); } if (r == OK && *skipwhite(p) != ']') @@ -6175,8 +6185,14 @@ compile_assign_unlet( garray_T *stack = &cctx->ctx_type_stack; int range = FALSE; - if (compile_assign_index(var_start, lhs, is_assign, &range, cctx) == FAIL) - return FAIL; + if (compile_assign_index(var_start, lhs, &range, cctx) == FAIL) + return FAIL; + if (is_assign && range && lhs->lhs_type != &t_blob + && lhs->lhs_type != &t_any) + { + semsg(_(e_cannot_use_range_with_assignment_str), var_start); + return FAIL; + } if (lhs->lhs_type == &t_any) { @@ -6213,15 +6229,24 @@ compile_assign_unlet( if (compile_load_lhs(lhs, var_start, rhs_type, cctx) == FAIL) return FAIL; - if (dest_type == VAR_LIST || dest_type == VAR_DICT || dest_type == VAR_ANY) + if (dest_type == VAR_LIST || dest_type == VAR_DICT + || dest_type == VAR_BLOB || dest_type == VAR_ANY) { if (is_assign) { - isn_T *isn = generate_instr_drop(cctx, ISN_STOREINDEX, 3); - - if (isn == NULL) - return FAIL; - isn->isn_arg.vartype = dest_type; + if (range) + { + if (generate_instr_drop(cctx, ISN_STORERANGE, 4) == NULL) + return FAIL; + } + else + { + isn_T *isn = generate_instr_drop(cctx, ISN_STOREINDEX, 3); + + if (isn == NULL) + return FAIL; + isn->isn_arg.vartype = dest_type; + } } else if (range) { @@ -6443,8 +6468,14 @@ compile_assignment(char_u *arg, exarg_T // Get member from list or dict. First compile the // index value. if (compile_assign_index(var_start, &lhs, - TRUE, &range, cctx) == FAIL) + &range, cctx) == FAIL) goto theend; + if (range) + { + semsg(_(e_cannot_use_range_with_assignment_str), + var_start); + return FAIL; + } // Get the member. if (compile_member(FALSE, cctx) == FAIL) @@ -9315,6 +9346,7 @@ delete_instr(isn_T *isn) case ISN_SLICE: case ISN_STORE: case ISN_STOREINDEX: + case ISN_STORERANGE: case ISN_STORENR: case ISN_STOREOUTER: case ISN_STOREREG: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2219,6 +2219,10 @@ call_def_function( clear_tv(tv); } } + else if (status == OK && dest_type == VAR_BLOB) + { + // TODO + } else { status = FAIL; @@ -2236,6 +2240,60 @@ call_def_function( } break; + // store value in blob range + case ISN_STORERANGE: + { + typval_T *tv_idx1 = STACK_TV_BOT(-3); + typval_T *tv_idx2 = STACK_TV_BOT(-2); + typval_T *tv_dest = STACK_TV_BOT(-1); + int status = OK; + + // Stack contains: + // -4 value to be stored + // -3 first index or "none" + // -2 second index or "none" + // -1 destination blob + tv = STACK_TV_BOT(-4); + if (tv_dest->v_type != VAR_BLOB) + { + status = FAIL; + emsg(_(e_blob_required)); + } + else + { + varnumber_T n1; + varnumber_T n2; + int error = FALSE; + + n1 = tv_get_number_chk(tv_idx1, &error); + if (error) + status = FAIL; + else + { + if (tv_idx2->v_type == VAR_SPECIAL + && tv_idx2->vval.v_number == VVAL_NONE) + n2 = blob_len(tv_dest->vval.v_blob) - 1; + else + n2 = tv_get_number_chk(tv_idx2, &error); + if (error) + status = FAIL; + else + status = blob_set_range(tv_dest->vval.v_blob, + n1, n2, tv); + } + } + + clear_tv(tv_idx1); + clear_tv(tv_idx2); + clear_tv(tv_dest); + ectx.ec_stack.ga_len -= 4; + clear_tv(tv); + + if (status == FAIL) + goto on_error; + } + break; + // load or store variable or argument from outer scope case ISN_LOADOUTER: case ISN_STOREOUTER: @@ -4362,6 +4420,10 @@ ex_disassemble(exarg_T *eap) } break; + case ISN_STORERANGE: + smsg("%4d STORERANGE", current); + break; + // constants case ISN_PUSHNR: smsg("%4d PUSHNR %lld", current,