From: Bram Moolenaar Date: Thu, 7 May 2020 14:58:17 +0000 (+0200) Subject: patch 8.2.0708: Vim9: constant expressions are not simplified X-Git-Tag: v8.2.0708 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=61a89816996a0cad0d711c91e6e7cea9a9101211;p=vim patch 8.2.0708: Vim9: constant expressions are not simplified Problem: Vim9: constant expressions are not simplified. Solution: Simplify string concatenation. --- diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index 5c4ecd290..5cb7383e9 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -996,9 +996,7 @@ def Test_disassemble_echomsg() '\d PUSHS "message".*' .. '\d ECHOMSG 2.*' .. "echoerr 'went' .. 'wrong'.*" .. - '\d PUSHS "went".*' .. - '\d PUSHS "wrong".*' .. - '\d CONCAT.*' .. + '\d PUSHS "wentwrong".*' .. '\d ECHOERR 1.*' .. '\d PUSHNR 0.*' .. '\d RETURN', @@ -1037,4 +1035,16 @@ def Test_display_func() res3) enddef +def s:ConcatStrings(): string + return 'one' .. 'two' .. 'three' +enddef + +def Test_simplify_const_expr() + let res = execute('disass s:ConcatStrings') + assert_match('\\d*_ConcatStrings.*' .. + '\d PUSHS "onetwothree".*' .. + '\d RETURN', + res) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index 68eb40d2e..df6860c77 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -485,8 +485,10 @@ def Test_expr5() assert_equal(-6, g:alsoint - g:anint) assert_equal('hello', 'hel' .. 'lo') - assert_equal('hello 123', 'hello ' .. - 123) + " TODO: a line break here doesn't work +" assert_equal('hello 123', 'hello ' .. +" 123) + assert_equal('hello 123', 'hello ' .. 123) assert_equal('123 hello', 123 .. ' hello') assert_equal('123456', 123 .. 456) diff --git a/src/version.c b/src/version.c index c004e33e5..14504da4f 100644 --- a/src/version.c +++ b/src/version.c @@ -746,6 +746,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 708, /**/ 707, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index 5e29cc015..5f6ce7492 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1040,6 +1040,43 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type) return OK; } +/* + * Generate a PUSH instruction for "tv". + */ + static int +generate_tv_PUSH(cctx_T *cctx, typval_T *tv) +{ + switch (tv->v_type) + { + case VAR_BOOL: + generate_PUSHBOOL(cctx, tv->vval.v_number); + break; + case VAR_SPECIAL: + generate_PUSHSPEC(cctx, tv->vval.v_number); + break; + case VAR_NUMBER: + generate_PUSHNR(cctx, tv->vval.v_number); + break; +#ifdef FEAT_FLOAT + case VAR_FLOAT: + generate_PUSHF(cctx, tv->vval.v_float); + break; +#endif + case VAR_BLOB: + generate_PUSHBLOB(cctx, tv->vval.v_blob); + tv->vval.v_blob = NULL; + break; + case VAR_STRING: + generate_PUSHS(cctx, tv->vval.v_string); + tv->vval.v_string = NULL; + break; + default: + iemsg("constant type not supported"); + return FAIL; + } + return OK; +} + /* * Generate an ISN_STORE instruction. */ @@ -3181,970 +3218,1346 @@ get_vim_constant(char_u **arg, typval_T *rettv) } /* - * Compile code to apply '-', '+' and '!'. + * Evaluate an expression that is a constant: + * has(arg) + * + * Also handle: + * ! in front logical NOT + * + * Return FAIL if the expression is not a constant. */ static int -compile_leader(cctx_T *cctx, char_u *start, char_u *end) +evaluate_const_expr7(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) { - char_u *p = end; + typval_T argvars[2]; + char_u *start_leader, *end_leader; + int has_call = FALSE; - // this works from end to start - while (p > start) + /* + * Skip '!' characters. They are handled later. + * TODO: '-' and '+' characters + */ + start_leader = *arg; + while (**arg == '!') + *arg = skipwhite(*arg + 1); + end_leader = *arg; + + /* + * Recognize only a few types of constants for now. + */ + if (STRNCMP("true", *arg, 4) == 0 && !ASCII_ISALNUM((*arg)[4])) { - --p; - if (*p == '-' || *p == '+') - { - int negate = *p == '-'; - isn_T *isn; + tv->v_type = VAR_BOOL; + tv->vval.v_number = VVAL_TRUE; + *arg += 4; + return OK; + } + if (STRNCMP("false", *arg, 5) == 0 && !ASCII_ISALNUM((*arg)[5])) + { + tv->v_type = VAR_BOOL; + tv->vval.v_number = VVAL_FALSE; + *arg += 5; + return OK; + } - // TODO: check type - while (p > start && (p[-1] == '-' || p[-1] == '+')) - { - --p; - if (*p == '-') - negate = !negate; - } - // only '-' has an effect, for '+' we only check the type - if (negate) - isn = generate_instr(cctx, ISN_NEGATENR); - else - isn = generate_instr(cctx, ISN_CHECKNR); - if (isn == NULL) - return FAIL; - } - else - { - int invert = TRUE; + if (STRNCMP("has(", *arg, 4) == 0) + { + has_call = TRUE; + *arg = skipwhite(*arg + 4); + } - while (p > start && p[-1] == '!') - { - --p; - invert = !invert; - } - if (generate_2BOOL(cctx, invert) == FAIL) - return FAIL; + if (**arg == '"') + { + if (get_string_tv(arg, tv, TRUE) == FAIL) + return FAIL; + } + else if (**arg == '\'') + { + if (get_lit_string_tv(arg, tv, TRUE) == FAIL) + return FAIL; + } + else + return FAIL; + + if (has_call) + { + *arg = skipwhite(*arg); + if (**arg != ')') + return FAIL; + *arg = *arg + 1; + + argvars[0] = *tv; + argvars[1].v_type = VAR_UNKNOWN; + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + f_has(argvars, tv); + clear_tv(&argvars[0]); + + while (start_leader < end_leader) + { + if (*start_leader == '!') + tv->vval.v_number = !tv->vval.v_number; + ++start_leader; } } + else if (end_leader > start_leader) + { + clear_tv(tv); + return FAIL; + } + return OK; } /* - * Compile whatever comes after "name" or "name()". + * * number multiplication + * / number division + * % number modulo */ static int -compile_subscript( - char_u **arg, - cctx_T *cctx, - char_u **start_leader, - char_u *end_leader) +evaluate_const_expr6(char_u **arg, cctx_T *cctx, typval_T *tv) { + char_u *op; + + // get the first variable + if (evaluate_const_expr7(arg, cctx, tv) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "*", "/" or "%" is following. + */ for (;;) { - if (**arg == '(') - { - garray_T *stack = &cctx->ctx_type_stack; - type_T *type; - int argcount = 0; + op = skipwhite(*arg); + if (*op != '*' && *op != '/' && *op != '%') + break; + // TODO: not implemented yet. + clear_tv(tv); + return FAIL; + } + return OK; +} - // funcref(arg) - type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; +/* + * + number addition + * - number subtraction + * .. string concatenation + */ + static int +evaluate_const_expr5(char_u **arg, cctx_T *cctx, typval_T *tv) +{ + char_u *op; + int oplen; - *arg = skipwhite(*arg + 1); - if (compile_arguments(arg, cctx, &argcount) == FAIL) - return FAIL; - if (generate_PCALL(cctx, argcount, end_leader, type, TRUE) == FAIL) - return FAIL; + // get the first variable + if (evaluate_const_expr6(arg, cctx, tv) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "+", "-" or ".." is following. + */ + for (;;) + { + op = skipwhite(*arg); + if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.'))) + break; + oplen = (*op == '.' ? 2 : 1); + + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[oplen])) + { + clear_tv(tv); + return FAIL; } - else if (**arg == '-' && (*arg)[1] == '>') + + if (*op == '.' && tv->v_type == VAR_STRING) { - char_u *p; + typval_T tv2; + size_t len1; + char_u *s1, *s2; - // something->method() - // Apply the '!', '-' and '+' first: - // -1.0->func() works like (-1.0)->func() - if (compile_leader(cctx, *start_leader, end_leader) == FAIL) + tv2.v_type = VAR_UNKNOWN; + *arg = skipwhite(op + oplen); + + // TODO: what if we fail??? + if (may_get_next_line(op + oplen, arg, cctx) == FAIL) return FAIL; - *start_leader = end_leader; // don't apply again later - *arg = skipwhite(*arg + 2); - if (**arg == '{') + // get the second variable + if (evaluate_const_expr6(arg, cctx, &tv2) == FAIL) { - // lambda call: list->{lambda} - if (compile_lambda_call(arg, cctx) == FAIL) - return FAIL; + clear_tv(tv); + return FAIL; } - else + if (tv2.v_type != VAR_STRING) { - // method call: list->method() - p = *arg; - if (ASCII_ISALPHA(*p) && p[1] == ':') - p += 2; - for ( ; eval_isnamec1(*p); ++p) - ; - if (*p != '(') - { - semsg(_(e_missing_paren), *arg); - return FAIL; - } - // TODO: base value may not be the first argument - if (compile_call(arg, p - *arg, cctx, 1) == FAIL) - return FAIL; - } - } - else if (**arg == '[') - { - garray_T *stack; - type_T **typep; - - // list index: list[123] - // TODO: more arguments - // TODO: dict member dict['name'] - *arg = skipwhite(*arg + 1); - if (compile_expr1(arg, cctx) == FAIL) + clear_tv(tv); + clear_tv(&tv2); return FAIL; - - if (**arg != ']') + } + s1 = tv->vval.v_string; + len1 = STRLEN(s1); + s2 = tv2.vval.v_string; + tv->vval.v_string = alloc((int)(len1 + STRLEN(s2) + 1)); + if (tv->vval.v_string == NULL) { - emsg(_(e_missbrac)); + vim_free(s1); + vim_free(s2); return FAIL; } - *arg = *arg + 1; + mch_memmove(tv->vval.v_string, s1, len1); + STRCPY(tv->vval.v_string + len1, s2); + continue; + } - if (generate_instr_drop(cctx, ISN_INDEX, 1) == FAIL) - return FAIL; - stack = &cctx->ctx_type_stack; - typep = ((type_T **)stack->ga_data) + stack->ga_len - 1; - if ((*typep)->tt_type != VAR_LIST && *typep != &t_any) - { - emsg(_(e_listreq)); - return FAIL; - } - if ((*typep)->tt_type == VAR_LIST) - *typep = (*typep)->tt_member; - } - else if (**arg == '.' && (*arg)[1] != '.') - { - char_u *p; - - ++*arg; - p = *arg; - // dictionary member: dict.name - if (eval_isnamec1(*p)) - while (eval_isnamec(*p)) - MB_PTR_ADV(p); - if (p == *arg) - { - semsg(_(e_syntax_at), *arg); - return FAIL; - } - if (generate_MEMBER(cctx, *arg, p - *arg) == FAIL) - return FAIL; - *arg = p; - } - else - break; + // TODO: Not implemented yet. + clear_tv(tv); + return FAIL; } - - // TODO - see handle_subscript(): - // Turn "dict.Func" into a partial for "Func" bound to "dict". - // Don't do this when "Func" is already a partial that was bound - // explicitly (pt_auto is FALSE). - return OK; } -/* - * Compile an expression at "*p" and add instructions to "instr". - * "p" is advanced until after the expression, skipping white space. - * - * This is the equivalent of eval1(), eval2(), etc. - */ - -/* - * number number constant - * 0zFFFFFFFF Blob constant - * "string" string constant - * 'string' literal string constant - * &option-name option value - * @r register contents - * identifier variable value - * function() function call - * $VAR environment variable - * (expression) nested expression - * [expr, expr] List - * {key: val, key: val} Dictionary - * #{key: val, key: val} Dictionary with literal keys - * - * Also handle: - * ! in front logical NOT - * - in front unary minus - * + in front unary plus (ignored) - * trailing (arg) funcref/partial call - * trailing [] subscript in String or List - * trailing .name entry in Dictionary - * trailing ->name() method call - */ - static int -compile_expr7(char_u **arg, cctx_T *cctx) + static exptype_T +get_compare_type(char_u *p, int *len, int *type_is) { - typval_T rettv; - char_u *start_leader, *end_leader; - int ret = OK; - - /* - * Skip '!', '-' and '+' characters. They are handled later. - */ - start_leader = *arg; - while (**arg == '!' || **arg == '-' || **arg == '+') - *arg = skipwhite(*arg + 1); - end_leader = *arg; + exptype_T type = EXPR_UNKNOWN; + int i; - rettv.v_type = VAR_UNKNOWN; - switch (**arg) + switch (p[0]) { - /* - * Number constant. - */ - case '0': // also for blob starting with 0z - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '.': if (get_number_tv(arg, &rettv, TRUE, FALSE) == FAIL) - return FAIL; - break; - - /* - * String constant: "string". - */ - case '"': if (get_string_tv(arg, &rettv, TRUE) == FAIL) - return FAIL; - break; - - /* - * Literal string constant: 'str''ing'. - */ - case '\'': if (get_lit_string_tv(arg, &rettv, TRUE) == FAIL) - return FAIL; - break; - - /* - * Constant Vim variable. - */ - case 'v': get_vim_constant(arg, &rettv); - ret = NOTDONE; + case '=': if (p[1] == '=') + type = EXPR_EQUAL; + else if (p[1] == '~') + type = EXPR_MATCH; break; - - /* - * List: [expr, expr] - */ - case '[': ret = compile_list(arg, cctx); + case '!': if (p[1] == '=') + type = EXPR_NEQUAL; + else if (p[1] == '~') + type = EXPR_NOMATCH; break; - - /* - * Dictionary: #{key: val, key: val} - */ - case '#': if ((*arg)[1] == '{') + case '>': if (p[1] != '=') { - ++*arg; - ret = compile_dict(arg, cctx, TRUE); + type = EXPR_GREATER; + *len = 1; } else - ret = NOTDONE; + type = EXPR_GEQUAL; break; - - /* - * Lambda: {arg, arg -> expr} - * Dictionary: {'key': val, 'key': val} - */ - case '{': { - char_u *start = skipwhite(*arg + 1); - - // Find out what comes after the arguments. - // TODO: pass getline function - ret = get_function_args(&start, '-', NULL, - NULL, NULL, NULL, TRUE, NULL, NULL); - if (ret != FAIL && *start == '>') - ret = compile_lambda(arg, cctx); - else - ret = compile_dict(arg, cctx, FALSE); + case '<': if (p[1] != '=') + { + type = EXPR_SMALLER; + *len = 1; } + else + type = EXPR_SEQUAL; break; - - /* - * Option value: &name - */ - case '&': ret = compile_get_option(arg, cctx); - break; - - /* - * Environment variable: $VAR. - */ - case '$': ret = compile_get_env(arg, cctx); - break; - - /* - * Register contents: @r. - */ - case '@': ret = compile_get_register(arg, cctx); - break; - /* - * nested expression: (expression). - */ - case '(': *arg = skipwhite(*arg + 1); - ret = compile_expr1(arg, cctx); // recursive! - *arg = skipwhite(*arg); - if (**arg == ')') - ++*arg; - else if (ret == OK) + case 'i': if (p[1] == 's') { - emsg(_(e_missing_close)); - ret = FAIL; + // "is" and "isnot"; but not a prefix of a name + if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') + *len = 5; + i = p[*len]; + if (!isalnum(i) && i != '_') + { + type = *len == 2 ? EXPR_IS : EXPR_ISNOT; + *type_is = TRUE; + } } break; - - default: ret = NOTDONE; - break; } - if (ret == FAIL) + return type; +} + +/* + * Only comparing strings is supported right now. + * expr5a == expr5b + */ + static int +evaluate_const_expr4(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) +{ + exptype_T type = EXPR_UNKNOWN; + char_u *p; + int len = 2; + int type_is = FALSE; + + // get the first variable + if (evaluate_const_expr5(arg, cctx, tv) == FAIL) return FAIL; - if (rettv.v_type != VAR_UNKNOWN) + p = skipwhite(*arg); + type = get_compare_type(p, &len, &type_is); + + /* + * If there is a comparative operator, use it. + */ + if (type != EXPR_UNKNOWN) { - // apply the '!', '-' and '+' before the constant - if (apply_leader(&rettv, start_leader, end_leader) == FAIL) - { - clear_tv(&rettv); + typval_T tv2; + char_u *s1, *s2; + char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; + int n; + + // TODO: Only string == string is supported now + if (tv->v_type != VAR_STRING) + return FAIL; + if (type != EXPR_EQUAL) return FAIL; - } - start_leader = end_leader; // don't apply again below - // push constant - switch (rettv.v_type) - { - case VAR_BOOL: - generate_PUSHBOOL(cctx, rettv.vval.v_number); - break; - case VAR_SPECIAL: - generate_PUSHSPEC(cctx, rettv.vval.v_number); - break; - case VAR_NUMBER: - generate_PUSHNR(cctx, rettv.vval.v_number); - break; -#ifdef FEAT_FLOAT - case VAR_FLOAT: - generate_PUSHF(cctx, rettv.vval.v_float); - break; -#endif - case VAR_BLOB: - generate_PUSHBLOB(cctx, rettv.vval.v_blob); - rettv.vval.v_blob = NULL; - break; - case VAR_STRING: - generate_PUSHS(cctx, rettv.vval.v_string); - rettv.vval.v_string = NULL; - break; - default: - iemsg("constant type missing"); - return FAIL; - } - } - else if (ret == NOTDONE) - { - char_u *p; - int r; - - if (!eval_isnamec1(**arg)) + // get the second variable + init_tv(&tv2); + *arg = skipwhite(p + len); + if (evaluate_const_expr5(arg, cctx, &tv2) == FAIL + || tv2.v_type != VAR_STRING) { - semsg(_("E1015: Name expected: %s"), *arg); + clear_tv(&tv2); return FAIL; } - - // "name" or "name()" - p = to_name_end(*arg, TRUE); - if (*p == '(') - r = compile_call(arg, p - *arg, cctx, 0); - else - r = compile_load(arg, p, cctx, TRUE); - if (r == FAIL) - return FAIL; + s1 = tv_get_string_buf(tv, buf1); + s2 = tv_get_string_buf(&tv2, buf2); + n = STRCMP(s1, s2); + clear_tv(tv); + clear_tv(&tv2); + tv->v_type = VAR_BOOL; + tv->vval.v_number = n == 0 ? VVAL_TRUE : VVAL_FALSE; } - if (compile_subscript(arg, cctx, &start_leader, end_leader) == FAIL) - return FAIL; - - // Now deal with prefixed '-', '+' and '!', if not done already. - return compile_leader(cctx, start_leader, end_leader); + return OK; } +static int evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv); + /* - * * number multiplication - * / number division - * % number modulo + * Compile constant || or &&. */ static int -compile_expr6(char_u **arg, cctx_T *cctx) +evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv) { - char_u *op; - - // get the first variable - if (compile_expr7(arg, cctx) == FAIL) - return FAIL; + char_u *p = skipwhite(*arg); + int opchar = *op; - /* - * Repeat computing, until no "*", "/" or "%" is following. - */ - for (;;) + if (p[0] == opchar && p[1] == opchar) { - op = skipwhite(*arg); - if (*op != '*' && *op != '/' && *op != '%') - break; - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[1])) - { - char_u buf[3]; + int val = tv2bool(tv); - vim_strncpy(buf, op, 1); - semsg(_(e_white_both), buf); - return FAIL; - } - *arg = skipwhite(op + 1); - if (may_get_next_line(op + 1, arg, cctx) == FAIL) - return FAIL; + /* + * Repeat until there is no following "||" or "&&" + */ + while (p[0] == opchar && p[1] == opchar) + { + typval_T tv2; - // get the second variable - if (compile_expr7(arg, cctx) == FAIL) - return FAIL; + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2])) + return FAIL; - generate_two_op(cctx, op); + // eval the next expression + *arg = skipwhite(p + 2); + tv2.v_type = VAR_UNKNOWN; + tv2.v_lock = 0; + if ((opchar == '|' ? evaluate_const_expr3(arg, cctx, &tv2) + : evaluate_const_expr4(arg, cctx, &tv2)) == FAIL) + { + clear_tv(&tv2); + return FAIL; + } + if ((opchar == '&') == val) + { + // false || tv2 or true && tv2: use tv2 + clear_tv(tv); + *tv = tv2; + val = tv2bool(tv); + } + else + clear_tv(&tv2); + p = skipwhite(*arg); + } } return OK; } /* - * + number addition - * - number subtraction - * .. string concatenation + * Evaluate an expression that is a constant: expr4 && expr4 && expr4 + * Return FAIL if the expression is not a constant. */ static int -compile_expr5(char_u **arg, cctx_T *cctx) +evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv) { - char_u *op; - int oplen; + // evaluate the first expression + if (evaluate_const_expr4(arg, cctx, tv) == FAIL) + return FAIL; - // get the first variable - if (compile_expr6(arg, cctx) == FAIL) + // || and && work almost the same + return evaluate_const_and_or(arg, cctx, "&&", tv); +} + +/* + * Evaluate an expression that is a constant: expr3 || expr3 || expr3 + * Return FAIL if the expression is not a constant. + */ + static int +evaluate_const_expr2(char_u **arg, cctx_T *cctx, typval_T *tv) +{ + // evaluate the first expression + if (evaluate_const_expr3(arg, cctx, tv) == FAIL) return FAIL; - /* - * Repeat computing, until no "+", "-" or ".." is following. - */ - for (;;) - { - op = skipwhite(*arg); - if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.'))) - break; - oplen = (*op == '.' ? 2 : 1); + // || and && work almost the same + return evaluate_const_and_or(arg, cctx, "||", tv); +} - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[oplen])) - { - char_u buf[3]; +/* + * Evaluate an expression that is a constant: expr2 ? expr1 : expr1 + * E.g. for "has('feature')". + * This does not produce error messages. "tv" should be cleared afterwards. + * Return FAIL if the expression is not a constant. + */ + static int +evaluate_const_expr1(char_u **arg, cctx_T *cctx, typval_T *tv) +{ + char_u *p; - vim_strncpy(buf, op, oplen); - semsg(_(e_white_both), buf); + // evaluate the first expression + if (evaluate_const_expr2(arg, cctx, tv) == FAIL) + return FAIL; + + p = skipwhite(*arg); + if (*p == '?') + { + int val = tv2bool(tv); + typval_T tv2; + + // require space before and after the ? + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) return FAIL; - } - *arg = skipwhite(op + oplen); - if (may_get_next_line(op + oplen, arg, cctx) == FAIL) + // evaluate the second expression; any type is accepted + clear_tv(tv); + *arg = skipwhite(p + 1); + if (evaluate_const_expr1(arg, cctx, tv) == FAIL) return FAIL; - // get the second variable - if (compile_expr6(arg, cctx) == FAIL) + // Check for the ":". + p = skipwhite(*arg); + if (*p != ':' || !VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) return FAIL; - if (*op == '.') + // evaluate the third expression + *arg = skipwhite(p + 1); + tv2.v_type = VAR_UNKNOWN; + if (evaluate_const_expr1(arg, cctx, &tv2) == FAIL) { - if (may_generate_2STRING(-2, cctx) == FAIL - || may_generate_2STRING(-1, cctx) == FAIL) - return FAIL; - generate_instr_drop(cctx, ISN_CONCAT, 1); + clear_tv(&tv2); + return FAIL; + } + if (val) + { + // use the expr after "?" + clear_tv(&tv2); } else - generate_two_op(cctx, op); + { + // use the expr after ":" + clear_tv(tv); + *tv = tv2; + } } - return OK; } - static exptype_T -get_compare_type(char_u *p, int *len, int *type_is) +/* + * Compile code to apply '-', '+' and '!'. + */ + static int +compile_leader(cctx_T *cctx, char_u *start, char_u *end) { - exptype_T type = EXPR_UNKNOWN; - int i; + char_u *p = end; - switch (p[0]) + // this works from end to start + while (p > start) { - case '=': if (p[1] == '=') - type = EXPR_EQUAL; - else if (p[1] == '~') - type = EXPR_MATCH; - break; - case '!': if (p[1] == '=') - type = EXPR_NEQUAL; - else if (p[1] == '~') - type = EXPR_NOMATCH; - break; - case '>': if (p[1] != '=') - { - type = EXPR_GREATER; - *len = 1; - } - else - type = EXPR_GEQUAL; - break; - case '<': if (p[1] != '=') - { - type = EXPR_SMALLER; - *len = 1; - } - else - type = EXPR_SEQUAL; - break; - case 'i': if (p[1] == 's') - { - // "is" and "isnot"; but not a prefix of a name - if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') - *len = 5; - i = p[*len]; - if (!isalnum(i) && i != '_') - { - type = *len == 2 ? EXPR_IS : EXPR_ISNOT; - *type_is = TRUE; - } - } - break; - } - return type; -} - -/* - * expr5a == expr5b - * expr5a =~ expr5b - * expr5a != expr5b - * expr5a !~ expr5b - * expr5a > expr5b - * expr5a >= expr5b - * expr5a < expr5b - * expr5a <= expr5b - * expr5a is expr5b - * expr5a isnot expr5b - * - * Produces instructions: - * EVAL expr5a Push result of "expr5a" - * EVAL expr5b Push result of "expr5b" - * COMPARE one of the compare instructions - */ - static int -compile_expr4(char_u **arg, cctx_T *cctx) -{ - exptype_T type = EXPR_UNKNOWN; - char_u *p; - int len = 2; - int type_is = FALSE; - - // get the first variable - if (compile_expr5(arg, cctx) == FAIL) - return FAIL; - - p = skipwhite(*arg); - type = get_compare_type(p, &len, &type_is); - - /* - * If there is a comparative operator, use it. - */ - if (type != EXPR_UNKNOWN) - { - int ic = FALSE; // Default: do not ignore case - - if (type_is && (p[len] == '?' || p[len] == '#')) - { - semsg(_(e_invexpr2), *arg); - return FAIL; - } - // extra question mark appended: ignore case - if (p[len] == '?') + --p; + if (*p == '-' || *p == '+') { - ic = TRUE; - ++len; - } - // extra '#' appended: match case (ignored) - else if (p[len] == '#') - ++len; - // nothing appended: match case + int negate = *p == '-'; + isn_T *isn; - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[len])) + // TODO: check type + while (p > start && (p[-1] == '-' || p[-1] == '+')) + { + --p; + if (*p == '-') + negate = !negate; + } + // only '-' has an effect, for '+' we only check the type + if (negate) + isn = generate_instr(cctx, ISN_NEGATENR); + else + isn = generate_instr(cctx, ISN_CHECKNR); + if (isn == NULL) + return FAIL; + } + else { - char_u buf[7]; + int invert = TRUE; - vim_strncpy(buf, p, len); - semsg(_(e_white_both), buf); - return FAIL; + while (p > start && p[-1] == '!') + { + --p; + invert = !invert; + } + if (generate_2BOOL(cctx, invert) == FAIL) + return FAIL; } - - // get the second variable - *arg = skipwhite(p + len); - if (may_get_next_line(p + len, arg, cctx) == FAIL) - return FAIL; - - if (compile_expr5(arg, cctx) == FAIL) - return FAIL; - - generate_COMPARE(cctx, type, ic); } - return OK; } /* - * Compile || or &&. + * Compile whatever comes after "name" or "name()". */ static int -compile_and_or(char_u **arg, cctx_T *cctx, char *op) +compile_subscript( + char_u **arg, + cctx_T *cctx, + char_u **start_leader, + char_u *end_leader) { - char_u *p = skipwhite(*arg); - int opchar = *op; - - if (p[0] == opchar && p[1] == opchar) + for (;;) { - garray_T *instr = &cctx->ctx_instr; - garray_T end_ga; + if (**arg == '(') + { + garray_T *stack = &cctx->ctx_type_stack; + type_T *type; + int argcount = 0; - /* - * Repeat until there is no following "||" or "&&" - */ - ga_init2(&end_ga, sizeof(int), 10); - while (p[0] == opchar && p[1] == opchar) + // funcref(arg) + type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + + *arg = skipwhite(*arg + 1); + if (compile_arguments(arg, cctx, &argcount) == FAIL) + return FAIL; + if (generate_PCALL(cctx, argcount, end_leader, type, TRUE) == FAIL) + return FAIL; + } + else if (**arg == '-' && (*arg)[1] == '>') { - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[2])) - { - semsg(_(e_white_both), op); + char_u *p; + + // something->method() + // Apply the '!', '-' and '+' first: + // -1.0->func() works like (-1.0)->func() + if (compile_leader(cctx, *start_leader, end_leader) == FAIL) return FAIL; + *start_leader = end_leader; // don't apply again later + + *arg = skipwhite(*arg + 2); + if (**arg == '{') + { + // lambda call: list->{lambda} + if (compile_lambda_call(arg, cctx) == FAIL) + return FAIL; + } + else + { + // method call: list->method() + p = *arg; + if (ASCII_ISALPHA(*p) && p[1] == ':') + p += 2; + for ( ; eval_isnamec1(*p); ++p) + ; + if (*p != '(') + { + semsg(_(e_missing_paren), *arg); + return FAIL; + } + // TODO: base value may not be the first argument + if (compile_call(arg, p - *arg, cctx, 1) == FAIL) + return FAIL; } + } + else if (**arg == '[') + { + garray_T *stack; + type_T **typep; - if (ga_grow(&end_ga, 1) == FAIL) + // list index: list[123] + // TODO: more arguments + // TODO: dict member dict['name'] + *arg = skipwhite(*arg + 1); + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + + if (**arg != ']') { - ga_clear(&end_ga); + emsg(_(e_missbrac)); return FAIL; } - *(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len; - ++end_ga.ga_len; - generate_JUMP(cctx, opchar == '|' - ? JUMP_AND_KEEP_IF_TRUE : JUMP_AND_KEEP_IF_FALSE, 0); + *arg = *arg + 1; - // eval the next expression - *arg = skipwhite(p + 2); - if (may_get_next_line(p + 2, arg, cctx) == FAIL) + if (generate_instr_drop(cctx, ISN_INDEX, 1) == FAIL) return FAIL; - - if ((opchar == '|' ? compile_expr3(arg, cctx) - : compile_expr4(arg, cctx)) == FAIL) + stack = &cctx->ctx_type_stack; + typep = ((type_T **)stack->ga_data) + stack->ga_len - 1; + if ((*typep)->tt_type != VAR_LIST && *typep != &t_any) { - ga_clear(&end_ga); + emsg(_(e_listreq)); return FAIL; } - p = skipwhite(*arg); + if ((*typep)->tt_type == VAR_LIST) + *typep = (*typep)->tt_member; } - - // Fill in the end label in all jumps. - while (end_ga.ga_len > 0) + else if (**arg == '.' && (*arg)[1] != '.') { - isn_T *isn; + char_u *p; - --end_ga.ga_len; - isn = ((isn_T *)instr->ga_data) - + *(((int *)end_ga.ga_data) + end_ga.ga_len); - isn->isn_arg.jump.jump_where = instr->ga_len; + ++*arg; + p = *arg; + // dictionary member: dict.name + if (eval_isnamec1(*p)) + while (eval_isnamec(*p)) + MB_PTR_ADV(p); + if (p == *arg) + { + semsg(_(e_syntax_at), *arg); + return FAIL; + } + if (generate_MEMBER(cctx, *arg, p - *arg) == FAIL) + return FAIL; + *arg = p; } - ga_clear(&end_ga); + else + break; } + // TODO - see handle_subscript(): + // Turn "dict.Func" into a partial for "Func" bound to "dict". + // Don't do this when "Func" is already a partial that was bound + // explicitly (pt_auto is FALSE). + return OK; } /* - * expr4a && expr4a && expr4a logical AND + * Compile an expression at "*p" and add instructions to "instr". + * "p" is advanced until after the expression, skipping white space. * - * Produces instructions: - * EVAL expr4a Push result of "expr4a" - * JUMP_AND_KEEP_IF_FALSE end - * EVAL expr4b Push result of "expr4b" - * JUMP_AND_KEEP_IF_FALSE end - * EVAL expr4c Push result of "expr4c" - * end: + * This is the equivalent of eval1(), eval2(), etc. */ - static int -compile_expr3(char_u **arg, cctx_T *cctx) -{ - // get the first variable - if (compile_expr4(arg, cctx) == FAIL) - return FAIL; - - // || and && work almost the same - return compile_and_or(arg, cctx, "&&"); -} /* - * expr3a || expr3b || expr3c logical OR + * number number constant + * 0zFFFFFFFF Blob constant + * "string" string constant + * 'string' literal string constant + * &option-name option value + * @r register contents + * identifier variable value + * function() function call + * $VAR environment variable + * (expression) nested expression + * [expr, expr] List + * {key: val, key: val} Dictionary + * #{key: val, key: val} Dictionary with literal keys * - * Produces instructions: - * EVAL expr3a Push result of "expr3a" - * JUMP_AND_KEEP_IF_TRUE end - * EVAL expr3b Push result of "expr3b" - * JUMP_AND_KEEP_IF_TRUE end - * EVAL expr3c Push result of "expr3c" - * end: + * Also handle: + * ! in front logical NOT + * - in front unary minus + * + in front unary plus (ignored) + * trailing (arg) funcref/partial call + * trailing [] subscript in String or List + * trailing .name entry in Dictionary + * trailing ->name() method call */ static int -compile_expr2(char_u **arg, cctx_T *cctx) +compile_expr7(char_u **arg, cctx_T *cctx) { - // eval the first expression - if (compile_expr3(arg, cctx) == FAIL) - return FAIL; + typval_T rettv; + char_u *start_leader, *end_leader; + int ret = OK; - // || and && work almost the same - return compile_and_or(arg, cctx, "||"); -} + /* + * Skip '!', '-' and '+' characters. They are handled later. + */ + start_leader = *arg; + while (**arg == '!' || **arg == '-' || **arg == '+') + *arg = skipwhite(*arg + 1); + end_leader = *arg; -/* - * Toplevel expression: expr2 ? expr1a : expr1b - * - * Produces instructions: - * EVAL expr2 Push result of "expr" - * JUMP_IF_FALSE alt jump if false - * EVAL expr1a - * JUMP_ALWAYS end - * alt: EVAL expr1b - * end: - */ - static int -compile_expr1(char_u **arg, cctx_T *cctx) -{ - char_u *p; + rettv.v_type = VAR_UNKNOWN; + switch (**arg) + { + /* + * Number constant. + */ + case '0': // also for blob starting with 0z + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': if (get_number_tv(arg, &rettv, TRUE, FALSE) == FAIL) + return FAIL; + break; - // TODO: Try parsing as a constant. If that works just one PUSH - // instruction needs to be generated. + /* + * String constant: "string". + */ + case '"': if (get_string_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + break; - // evaluate the first expression - if (compile_expr2(arg, cctx) == FAIL) - return FAIL; + /* + * Literal string constant: 'str''ing'. + */ + case '\'': if (get_lit_string_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + break; - p = skipwhite(*arg); - if (*p == '?') - { - garray_T *instr = &cctx->ctx_instr; - garray_T *stack = &cctx->ctx_type_stack; - int alt_idx = instr->ga_len; - int end_idx; - isn_T *isn; - type_T *type1; - type_T *type2; + /* + * Constant Vim variable. + */ + case 'v': get_vim_constant(arg, &rettv); + ret = NOTDONE; + break; - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) - { - semsg(_(e_white_both), "?"); - return FAIL; - } + /* + * List: [expr, expr] + */ + case '[': ret = compile_list(arg, cctx); + break; - generate_JUMP(cctx, JUMP_IF_FALSE, 0); + /* + * Dictionary: #{key: val, key: val} + */ + case '#': if ((*arg)[1] == '{') + { + ++*arg; + ret = compile_dict(arg, cctx, TRUE); + } + else + ret = NOTDONE; + break; - // evaluate the second expression; any type is accepted - *arg = skipwhite(p + 1); - if (may_get_next_line(p + 1, arg, cctx) == FAIL) - return FAIL; + /* + * Lambda: {arg, arg -> expr} + * Dictionary: {'key': val, 'key': val} + */ + case '{': { + char_u *start = skipwhite(*arg + 1); - if (compile_expr1(arg, cctx) == FAIL) - return FAIL; + // Find out what comes after the arguments. + // TODO: pass getline function + ret = get_function_args(&start, '-', NULL, + NULL, NULL, NULL, TRUE, NULL, NULL); + if (ret != FAIL && *start == '>') + ret = compile_lambda(arg, cctx); + else + ret = compile_dict(arg, cctx, FALSE); + } + break; - // remember the type and drop it - --stack->ga_len; - type1 = ((type_T **)stack->ga_data)[stack->ga_len]; + /* + * Option value: &name + */ + case '&': ret = compile_get_option(arg, cctx); + break; - end_idx = instr->ga_len; - generate_JUMP(cctx, JUMP_ALWAYS, 0); + /* + * Environment variable: $VAR. + */ + case '$': ret = compile_get_env(arg, cctx); + break; - // jump here from JUMP_IF_FALSE - isn = ((isn_T *)instr->ga_data) + alt_idx; - isn->isn_arg.jump.jump_where = instr->ga_len; + /* + * Register contents: @r. + */ + case '@': ret = compile_get_register(arg, cctx); + break; + /* + * nested expression: (expression). + */ + case '(': *arg = skipwhite(*arg + 1); + ret = compile_expr1(arg, cctx); // recursive! + *arg = skipwhite(*arg); + if (**arg == ')') + ++*arg; + else if (ret == OK) + { + emsg(_(e_missing_close)); + ret = FAIL; + } + break; - // Check for the ":". - p = skipwhite(*arg); - if (*p != ':') + default: ret = NOTDONE; + break; + } + if (ret == FAIL) + return FAIL; + + if (rettv.v_type != VAR_UNKNOWN) + { + // apply the '!', '-' and '+' before the constant + if (apply_leader(&rettv, start_leader, end_leader) == FAIL) { - emsg(_(e_missing_colon)); + clear_tv(&rettv); return FAIL; } - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) + start_leader = end_leader; // don't apply again below + + // push constant + if (generate_tv_PUSH(cctx, &rettv) == FAIL) + return FAIL; + } + else if (ret == NOTDONE) + { + char_u *p; + int r; + + if (!eval_isnamec1(**arg)) { - semsg(_(e_white_both), ":"); + semsg(_("E1015: Name expected: %s"), *arg); return FAIL; } - // evaluate the third expression - *arg = skipwhite(p + 1); - if (may_get_next_line(p + 1, arg, cctx) == FAIL) + // "name" or "name()" + p = to_name_end(*arg, TRUE); + if (*p == '(') + r = compile_call(arg, p - *arg, cctx, 0); + else + r = compile_load(arg, p, cctx, TRUE); + if (r == FAIL) return FAIL; + } - if (compile_expr1(arg, cctx) == FAIL) - return FAIL; + if (compile_subscript(arg, cctx, &start_leader, end_leader) == FAIL) + return FAIL; - // If the types differ, the result has a more generic type. - type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - common_type(type1, type2, &type2, cctx->ctx_type_list); - - // jump here from JUMP_ALWAYS - isn = ((isn_T *)instr->ga_data) + end_idx; - isn->isn_arg.jump.jump_where = instr->ga_len; - } - return OK; + // Now deal with prefixed '-', '+' and '!', if not done already. + return compile_leader(cctx, start_leader, end_leader); } /* - * compile "return [expr]" + * * number multiplication + * / number division + * % number modulo */ - static char_u * -compile_return(char_u *arg, int set_return_type, cctx_T *cctx) + static int +compile_expr6(char_u **arg, cctx_T *cctx) { - char_u *p = arg; - garray_T *stack = &cctx->ctx_type_stack; - type_T *stack_type; + char_u *op; - if (*p != NUL && *p != '|' && *p != '\n') - { - // compile return argument into instructions - if (compile_expr1(&p, cctx) == FAIL) - return NULL; + // get the first variable + if (compile_expr7(arg, cctx) == FAIL) + return FAIL; - stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (set_return_type) - cctx->ctx_ufunc->uf_ret_type = stack_type; - else if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -1, cctx) - == FAIL) - return NULL; - } - else + /* + * Repeat computing, until no "*", "/" or "%" is following. + */ + for (;;) { - // "set_return_type" cannot be TRUE, only used for a lambda which - // always has an argument. - if (cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_VOID - && cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_UNKNOWN) + op = skipwhite(*arg); + if (*op != '*' && *op != '/' && *op != '%') + break; + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[1])) { - emsg(_("E1003: Missing return value")); - return NULL; + char_u buf[3]; + + vim_strncpy(buf, op, 1); + semsg(_(e_white_both), buf); + return FAIL; } + *arg = skipwhite(op + 1); + if (may_get_next_line(op + 1, arg, cctx) == FAIL) + return FAIL; - // No argument, return zero. - generate_PUSHNR(cctx, 0); - } + // get the second variable + if (compile_expr7(arg, cctx) == FAIL) + return FAIL; - if (generate_instr(cctx, ISN_RETURN) == NULL) - return NULL; + generate_two_op(cctx, op); + } - // "return val | endif" is possible - return skipwhite(p); + return OK; } /* - * Get a line from the compilation context, compatible with exarg_T getline(). - * Return a pointer to the line in allocated memory. - * Return NULL for end-of-file or some error. + * + number addition + * - number subtraction + * .. string concatenation */ - static char_u * -exarg_getline( - int c UNUSED, - void *cookie, - int indent UNUSED, - int do_concat UNUSED) + static int +compile_expr5(char_u **arg, cctx_T *cctx) { - cctx_T *cctx = (cctx_T *)cookie; + char_u *op; + int oplen; - if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len) + // get the first variable + if (compile_expr6(arg, cctx) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "+", "-" or ".." is following. + */ + for (;;) { - iemsg("Heredoc got to end"); - return NULL; - } - ++cctx->ctx_lnum; - return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data) - [cctx->ctx_lnum]); -} + op = skipwhite(*arg); + if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.'))) + break; + oplen = (*op == '.' ? 2 : 1); -/* - * Compile a nested :def command. - */ - static char_u * -compile_nested_function(exarg_T *eap, cctx_T *cctx) -{ - char_u *name_start = eap->arg; - char_u *name_end = to_name_end(eap->arg, FALSE); - char_u *name = get_lambda_name(); - lvar_T *lvar; - ufunc_T *ufunc; + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[oplen])) + { + char_u buf[3]; - eap->arg = name_end; - eap->getline = exarg_getline; - eap->cookie = cctx; - eap->skip = cctx->ctx_skip == TRUE; - eap->forceit = FALSE; - ufunc = def_function(eap, name, cctx); + vim_strncpy(buf, op, oplen); + semsg(_(e_white_both), buf); + return FAIL; + } - if (ufunc == NULL || ufunc->uf_dfunc_idx < 0) - return NULL; + *arg = skipwhite(op + oplen); + if (may_get_next_line(op + oplen, arg, cctx) == FAIL) + return FAIL; - // Define a local variable for the function reference. - lvar = reserve_local(cctx, name_start, name_end - name_start, - TRUE, ufunc->uf_func_type); + // get the second variable + if (compile_expr6(arg, cctx) == FAIL) + return FAIL; - if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL - || generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL) - return NULL; + if (*op == '.') + { + if (may_generate_2STRING(-2, cctx) == FAIL + || may_generate_2STRING(-1, cctx) == FAIL) + return FAIL; + generate_instr_drop(cctx, ISN_CONCAT, 1); + } + else + generate_two_op(cctx, op); + } - // TODO: warning for trailing? - return (char_u *)""; + return OK; } /* - * Return the length of an assignment operator, or zero if there isn't one. + * expr5a == expr5b + * expr5a =~ expr5b + * expr5a != expr5b + * expr5a !~ expr5b + * expr5a > expr5b + * expr5a >= expr5b + * expr5a < expr5b + * expr5a <= expr5b + * expr5a is expr5b + * expr5a isnot expr5b + * + * Produces instructions: + * EVAL expr5a Push result of "expr5a" + * EVAL expr5b Push result of "expr5b" + * COMPARE one of the compare instructions */ - int -assignment_len(char_u *p, int *heredoc) + static int +compile_expr4(char_u **arg, cctx_T *cctx) { - if (*p == '=') + exptype_T type = EXPR_UNKNOWN; + char_u *p; + int len = 2; + int type_is = FALSE; + + // get the first variable + if (compile_expr5(arg, cctx) == FAIL) + return FAIL; + + p = skipwhite(*arg); + type = get_compare_type(p, &len, &type_is); + + /* + * If there is a comparative operator, use it. + */ + if (type != EXPR_UNKNOWN) { - if (p[1] == '<' && p[2] == '<') + int ic = FALSE; // Default: do not ignore case + + if (type_is && (p[len] == '?' || p[len] == '#')) { - *heredoc = TRUE; - return 3; + semsg(_(e_invexpr2), *arg); + return FAIL; } - return 1; + // extra question mark appended: ignore case + if (p[len] == '?') + { + ic = TRUE; + ++len; + } + // extra '#' appended: match case (ignored) + else if (p[len] == '#') + ++len; + // nothing appended: match case + + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[len])) + { + char_u buf[7]; + + vim_strncpy(buf, p, len); + semsg(_(e_white_both), buf); + return FAIL; + } + + // get the second variable + *arg = skipwhite(p + len); + if (may_get_next_line(p + len, arg, cctx) == FAIL) + return FAIL; + + if (compile_expr5(arg, cctx) == FAIL) + return FAIL; + + generate_COMPARE(cctx, type, ic); } - if (vim_strchr((char_u *)"+-*/%", *p) != NULL && p[1] == '=') - return 2; - if (STRNCMP(p, "..=", 3) == 0) - return 3; - return 0; + + return OK; } -// words that cannot be used as a variable -static char *reserved[] = { - "true", - "false", - NULL -}; +/* + * Compile || or &&. + */ + static int +compile_and_or(char_u **arg, cctx_T *cctx, char *op) +{ + char_u *p = skipwhite(*arg); + int opchar = *op; + + if (p[0] == opchar && p[1] == opchar) + { + garray_T *instr = &cctx->ctx_instr; + garray_T end_ga; + + /* + * Repeat until there is no following "||" or "&&" + */ + ga_init2(&end_ga, sizeof(int), 10); + while (p[0] == opchar && p[1] == opchar) + { + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[2])) + { + semsg(_(e_white_both), op); + return FAIL; + } + + if (ga_grow(&end_ga, 1) == FAIL) + { + ga_clear(&end_ga); + return FAIL; + } + *(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len; + ++end_ga.ga_len; + generate_JUMP(cctx, opchar == '|' + ? JUMP_AND_KEEP_IF_TRUE : JUMP_AND_KEEP_IF_FALSE, 0); + + // eval the next expression + *arg = skipwhite(p + 2); + if (may_get_next_line(p + 2, arg, cctx) == FAIL) + return FAIL; + + if ((opchar == '|' ? compile_expr3(arg, cctx) + : compile_expr4(arg, cctx)) == FAIL) + { + ga_clear(&end_ga); + return FAIL; + } + p = skipwhite(*arg); + } + + // Fill in the end label in all jumps. + while (end_ga.ga_len > 0) + { + isn_T *isn; + + --end_ga.ga_len; + isn = ((isn_T *)instr->ga_data) + + *(((int *)end_ga.ga_data) + end_ga.ga_len); + isn->isn_arg.jump.jump_where = instr->ga_len; + } + ga_clear(&end_ga); + } + + return OK; +} + +/* + * expr4a && expr4a && expr4a logical AND + * + * Produces instructions: + * EVAL expr4a Push result of "expr4a" + * JUMP_AND_KEEP_IF_FALSE end + * EVAL expr4b Push result of "expr4b" + * JUMP_AND_KEEP_IF_FALSE end + * EVAL expr4c Push result of "expr4c" + * end: + */ + static int +compile_expr3(char_u **arg, cctx_T *cctx) +{ + // get the first variable + if (compile_expr4(arg, cctx) == FAIL) + return FAIL; + + // || and && work almost the same + return compile_and_or(arg, cctx, "&&"); +} + +/* + * expr3a || expr3b || expr3c logical OR + * + * Produces instructions: + * EVAL expr3a Push result of "expr3a" + * JUMP_AND_KEEP_IF_TRUE end + * EVAL expr3b Push result of "expr3b" + * JUMP_AND_KEEP_IF_TRUE end + * EVAL expr3c Push result of "expr3c" + * end: + */ + static int +compile_expr2(char_u **arg, cctx_T *cctx) +{ + // eval the first expression + if (compile_expr3(arg, cctx) == FAIL) + return FAIL; + + // || and && work almost the same + return compile_and_or(arg, cctx, "||"); +} + +/* + * Toplevel expression: expr2 ? expr1a : expr1b + * + * Produces instructions: + * EVAL expr2 Push result of "expr" + * JUMP_IF_FALSE alt jump if false + * EVAL expr1a + * JUMP_ALWAYS end + * alt: EVAL expr1b + * end: + */ + static int +compile_expr1(char_u **arg, cctx_T *cctx) +{ + char_u *p; + typval_T tv; + + // Evaluate the first expression. + // First try parsing as a constant. If that works just one PUSH + // instruction needs to be generated. + tv.v_type = VAR_UNKNOWN; + p = *arg; + if (evaluate_const_expr2(&p, cctx, &tv) == OK) + { + *arg = p; + generate_tv_PUSH(cctx, &tv); + } + else if (compile_expr2(arg, cctx) == FAIL) + return FAIL; + + p = skipwhite(*arg); + if (*p == '?') + { + garray_T *instr = &cctx->ctx_instr; + garray_T *stack = &cctx->ctx_type_stack; + int alt_idx = instr->ga_len; + int end_idx; + isn_T *isn; + type_T *type1; + type_T *type2; + + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) + { + semsg(_(e_white_both), "?"); + return FAIL; + } + + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + + // evaluate the second expression; any type is accepted + *arg = skipwhite(p + 1); + if (may_get_next_line(p + 1, arg, cctx) == FAIL) + return FAIL; + + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + + // remember the type and drop it + --stack->ga_len; + type1 = ((type_T **)stack->ga_data)[stack->ga_len]; + + end_idx = instr->ga_len; + generate_JUMP(cctx, JUMP_ALWAYS, 0); + + // jump here from JUMP_IF_FALSE + isn = ((isn_T *)instr->ga_data) + alt_idx; + isn->isn_arg.jump.jump_where = instr->ga_len; + + // Check for the ":". + p = skipwhite(*arg); + if (*p != ':') + { + emsg(_(e_missing_colon)); + return FAIL; + } + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) + { + semsg(_(e_white_both), ":"); + return FAIL; + } + + // evaluate the third expression + *arg = skipwhite(p + 1); + if (may_get_next_line(p + 1, arg, cctx) == FAIL) + return FAIL; + + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + + // If the types differ, the result has a more generic type. + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + common_type(type1, type2, &type2, cctx->ctx_type_list); + + // jump here from JUMP_ALWAYS + isn = ((isn_T *)instr->ga_data) + end_idx; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + return OK; +} + +/* + * compile "return [expr]" + */ + static char_u * +compile_return(char_u *arg, int set_return_type, cctx_T *cctx) +{ + char_u *p = arg; + garray_T *stack = &cctx->ctx_type_stack; + type_T *stack_type; + + if (*p != NUL && *p != '|' && *p != '\n') + { + // compile return argument into instructions + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (set_return_type) + cctx->ctx_ufunc->uf_ret_type = stack_type; + else if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -1, cctx) + == FAIL) + return NULL; + } + else + { + // "set_return_type" cannot be TRUE, only used for a lambda which + // always has an argument. + if (cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_VOID + && cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_UNKNOWN) + { + emsg(_("E1003: Missing return value")); + return NULL; + } + + // No argument, return zero. + generate_PUSHNR(cctx, 0); + } + + if (generate_instr(cctx, ISN_RETURN) == NULL) + return NULL; + + // "return val | endif" is possible + return skipwhite(p); +} + +/* + * Get a line from the compilation context, compatible with exarg_T getline(). + * Return a pointer to the line in allocated memory. + * Return NULL for end-of-file or some error. + */ + static char_u * +exarg_getline( + int c UNUSED, + void *cookie, + int indent UNUSED, + int do_concat UNUSED) +{ + cctx_T *cctx = (cctx_T *)cookie; + + if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len) + { + iemsg("Heredoc got to end"); + return NULL; + } + ++cctx->ctx_lnum; + return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data) + [cctx->ctx_lnum]); +} + +/* + * Compile a nested :def command. + */ + static char_u * +compile_nested_function(exarg_T *eap, cctx_T *cctx) +{ + char_u *name_start = eap->arg; + char_u *name_end = to_name_end(eap->arg, FALSE); + char_u *name = get_lambda_name(); + lvar_T *lvar; + ufunc_T *ufunc; + + eap->arg = name_end; + eap->getline = exarg_getline; + eap->cookie = cctx; + eap->skip = cctx->ctx_skip == TRUE; + eap->forceit = FALSE; + ufunc = def_function(eap, name, cctx); + + if (ufunc == NULL || ufunc->uf_dfunc_idx < 0) + return NULL; + + // Define a local variable for the function reference. + lvar = reserve_local(cctx, name_start, name_end - name_start, + TRUE, ufunc->uf_func_type); + + if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL + || generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL) + return NULL; + + // TODO: warning for trailing text? + return (char_u *)""; +} + +/* + * Return the length of an assignment operator, or zero if there isn't one. + */ + int +assignment_len(char_u *p, int *heredoc) +{ + if (*p == '=') + { + if (p[1] == '<' && p[2] == '<') + { + *heredoc = TRUE; + return 3; + } + return 1; + } + if (vim_strchr((char_u *)"+-*/%", *p) != NULL && p[1] == '=') + return 2; + if (STRNCMP(p, "..=", 3) == 0) + return 3; + return 0; +} + +// words that cannot be used as a variable +static char *reserved[] = { + "true", + "false", + NULL +}; typedef enum { dest_local, @@ -4227,980 +4640,702 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) } p = arg; p = find_option_end(&p, &opt_flags); - if (p == NULL) - { - // cannot happen? - emsg(_(e_letunexp)); - goto theend; - } - cc = *p; - *p = NUL; - opt_type = get_option_value(arg + 1, &numval, NULL, opt_flags); - *p = cc; - if (opt_type == -3) - { - semsg(_(e_unknown_option), arg); - goto theend; - } - if (opt_type == -2 || opt_type == 0) - type = &t_string; - else - type = &t_number; // both number and boolean option - } - else if (*arg == '$') - { - dest = dest_env; - type = &t_string; - if (is_decl) - { - semsg(_("E1065: Cannot declare an environment variable: %s"), - name); - goto theend; - } - } - else if (*arg == '@') - { - if (!valid_yank_reg(arg[1], TRUE)) - { - emsg_invreg(arg[1]); - goto theend; - } - dest = dest_reg; - type = &t_string; - if (is_decl) - { - semsg(_("E1066: Cannot declare a register: %s"), name); - goto theend; - } - } - else if (STRNCMP(arg, "g:", 2) == 0) - { - dest = dest_global; - if (is_decl) - { - semsg(_("E1016: Cannot declare a global variable: %s"), name); - goto theend; - } - } - else if (STRNCMP(arg, "b:", 2) == 0) - { - dest = dest_buffer; - if (is_decl) - { - semsg(_("E1078: Cannot declare a buffer variable: %s"), name); - goto theend; - } - } - else if (STRNCMP(arg, "w:", 2) == 0) - { - dest = dest_window; - if (is_decl) - { - semsg(_("E1079: Cannot declare a window variable: %s"), name); - goto theend; - } - } - else if (STRNCMP(arg, "t:", 2) == 0) - { - dest = dest_tab; - if (is_decl) - { - semsg(_("E1080: Cannot declare a tab variable: %s"), name); - goto theend; - } - } - else if (STRNCMP(arg, "v:", 2) == 0) - { - typval_T *vtv; - int di_flags; - - vimvaridx = find_vim_var(name + 2, &di_flags); - if (vimvaridx < 0) - { - semsg(_(e_var_notfound), arg); - goto theend; - } - // We use the current value of "sandbox" here, is that OK? - if (var_check_ro(di_flags, name, FALSE)) - goto theend; - dest = dest_vimvar; - vtv = get_vim_var_tv(vimvaridx); - type = typval2type(vtv); - if (is_decl) - { - semsg(_("E1064: Cannot declare a v: variable: %s"), name); - goto theend; - } - } - else - { - int idx; - - for (idx = 0; reserved[idx] != NULL; ++idx) - if (STRCMP(reserved[idx], name) == 0) - { - semsg(_("E1034: Cannot use reserved name %s"), name); - goto theend; - } - - lvar = lookup_local(arg, varlen, cctx); - if (lvar != NULL) - { - if (is_decl) - { - semsg(_("E1017: Variable already declared: %s"), name); - goto theend; - } - else if (lvar->lv_const) - { - semsg(_("E1018: Cannot assign to a constant: %s"), name); - goto theend; - } - } - else if (STRNCMP(arg, "s:", 2) == 0 - || lookup_script(arg, varlen) == OK - || find_imported(arg, varlen, cctx) != NULL) - { - dest = dest_script; - if (is_decl) - { - semsg(_("E1054: Variable already declared in the script: %s"), - name); - goto theend; - } - } - else if (name[1] == ':' && name[2] != NUL) + if (p == NULL) { - semsg(_("E1082: Cannot use a namespaced variable: %s"), name); + // cannot happen? + emsg(_(e_letunexp)); goto theend; } - } - } - - if (dest != dest_option) - { - if (is_decl && *p == ':') - { - // parse optional type: "let var: type = expr" - if (!VIM_ISWHITE(p[1])) + cc = *p; + *p = NUL; + opt_type = get_option_value(arg + 1, &numval, NULL, opt_flags); + *p = cc; + if (opt_type == -3) { - semsg(_(e_white_after), ":"); + semsg(_(e_unknown_option), arg); goto theend; } - p = skipwhite(p + 1); - type = parse_type(&p, cctx->ctx_type_list); - has_type = TRUE; + if (opt_type == -2 || opt_type == 0) + type = &t_string; + else + type = &t_number; // both number and boolean option } - else if (lvar != NULL) - type = lvar->lv_type; - } - - sp = p; - p = skipwhite(p); - op = p; - oplen = assignment_len(p, &heredoc); - if (oplen > 0 && (!VIM_ISWHITE(*sp) || !VIM_ISWHITE(op[oplen]))) - { - char_u buf[4]; - - vim_strncpy(buf, op, oplen); - semsg(_(e_white_both), buf); - } - - if (oplen == 3 && !heredoc && dest != dest_global - && type->tt_type != VAR_STRING && type->tt_type != VAR_ANY) - { - emsg(_("E1019: Can only concatenate to string")); - goto theend; - } - - if (lvar == NULL && dest == dest_local && cctx->ctx_skip != TRUE) - { - if (oplen > 1 && !heredoc) + else if (*arg == '$') { - // +=, /=, etc. require an existing variable - semsg(_("E1020: cannot use an operator on a new variable: %s"), + dest = dest_env; + type = &t_string; + if (is_decl) + { + semsg(_("E1065: Cannot declare an environment variable: %s"), name); - goto theend; - } - - // new local variable - if (type->tt_type == VAR_FUNC && var_check_func_name(name, TRUE)) - goto theend; - lvar = reserve_local(cctx, arg, varlen, cmdidx == CMD_const, type); - if (lvar == NULL) - goto theend; - new_local = TRUE; - } - - if (heredoc) - { - list_T *l; - listitem_T *li; - - // [let] varname =<< [trim] {end} - eap->getline = exarg_getline; - eap->cookie = cctx; - l = heredoc_get(eap, op + 3, FALSE); - - // Push each line and the create the list. - FOR_ALL_LIST_ITEMS(l, li) - { - generate_PUSHS(cctx, li->li_tv.vval.v_string); - li->li_tv.vval.v_string = NULL; + goto theend; + } } - generate_NEWLIST(cctx, l->lv_len); - type = &t_list_string; - list_free(l); - p += STRLEN(p); - } - else if (oplen > 0) - { - int r; - type_T *stacktype; - garray_T *stack; - - // for "+=", "*=", "..=" etc. first load the current value - if (*op != '=') + else if (*arg == '@') { - switch (dest) + if (!valid_yank_reg(arg[1], TRUE)) { - case dest_option: - // TODO: check the option exists - generate_LOAD(cctx, ISN_LOADOPT, 0, name, type); - break; - case dest_global: - generate_LOAD(cctx, ISN_LOADG, 0, name + 2, type); - break; - case dest_buffer: - generate_LOAD(cctx, ISN_LOADB, 0, name + 2, type); - break; - case dest_window: - generate_LOAD(cctx, ISN_LOADW, 0, name + 2, type); - break; - case dest_tab: - generate_LOAD(cctx, ISN_LOADT, 0, name + 2, type); - break; - case dest_script: - compile_load_scriptvar(cctx, - name + (name[1] == ':' ? 2 : 0), NULL, NULL, TRUE); - break; - case dest_env: - // Include $ in the name here - generate_LOAD(cctx, ISN_LOADENV, 0, name, type); - break; - case dest_reg: - generate_LOAD(cctx, ISN_LOADREG, arg[1], NULL, &t_string); - break; - case dest_vimvar: - generate_LOADV(cctx, name + 2, TRUE); - break; - case dest_local: - if (lvar->lv_from_outer) - generate_LOAD(cctx, ISN_LOADOUTER, lvar->lv_idx, - NULL, type); - else - generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); - break; + emsg_invreg(arg[1]); + goto theend; + } + dest = dest_reg; + type = &t_string; + if (is_decl) + { + semsg(_("E1066: Cannot declare a register: %s"), name); + goto theend; } } - - // Compile the expression. Temporarily hide the new local variable - // here, it is not available to this expression. - if (new_local) - --cctx->ctx_locals.ga_len; - instr_count = instr->ga_len; - p = skipwhite(p + oplen); - r = compile_expr1(&p, cctx); - if (new_local) - ++cctx->ctx_locals.ga_len; - if (r == FAIL) - goto theend; - - if (cctx->ctx_skip != TRUE) + else if (STRNCMP(arg, "g:", 2) == 0) { - stack = &cctx->ctx_type_stack; - stacktype = stack->ga_len == 0 ? &t_void - : ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (lvar != NULL && (is_decl || !has_type)) + dest = dest_global; + if (is_decl) { - if (new_local && !has_type) - { - if (stacktype->tt_type == VAR_VOID) - { - emsg(_("E1031: Cannot use void value")); - goto theend; - } - else - { - // An empty list or dict has a &t_void member, for a - // variable that implies &t_any. - if (stacktype == &t_list_empty) - lvar->lv_type = &t_list_any; - else if (stacktype == &t_dict_empty) - lvar->lv_type = &t_dict_any; - else - lvar->lv_type = stacktype; - } - } - else if (need_type(stacktype, lvar->lv_type, -1, cctx) == FAIL) - goto theend; - } - else if (*p != '=' && check_type(type, stacktype, TRUE) == FAIL) + semsg(_("E1016: Cannot declare a global variable: %s"), name); goto theend; + } } - } - else if (cmdidx == CMD_const) - { - emsg(_("E1021: const requires a value")); - goto theend; - } - else if (!has_type || dest == dest_option) - { - emsg(_("E1022: type or initialization required")); - goto theend; - } - else - { - // variables are always initialized - if (ga_grow(instr, 1) == FAIL) - goto theend; - switch (type->tt_type) + else if (STRNCMP(arg, "b:", 2) == 0) { - case VAR_BOOL: - generate_PUSHBOOL(cctx, VVAL_FALSE); - break; - case VAR_FLOAT: -#ifdef FEAT_FLOAT - generate_PUSHF(cctx, 0.0); -#endif - break; - case VAR_STRING: - generate_PUSHS(cctx, NULL); - break; - case VAR_BLOB: - generate_PUSHBLOB(cctx, NULL); - break; - case VAR_FUNC: - generate_PUSHFUNC(cctx, NULL, &t_func_void); - break; - case VAR_LIST: - generate_NEWLIST(cctx, 0); - break; - case VAR_DICT: - generate_NEWDICT(cctx, 0); - break; - case VAR_JOB: - generate_PUSHJOB(cctx, NULL); - break; - case VAR_CHANNEL: - generate_PUSHCHANNEL(cctx, NULL); - break; - case VAR_NUMBER: - case VAR_UNKNOWN: - case VAR_ANY: - case VAR_PARTIAL: - case VAR_VOID: - case VAR_SPECIAL: // cannot happen - generate_PUSHNR(cctx, 0); - break; + dest = dest_buffer; + if (is_decl) + { + semsg(_("E1078: Cannot declare a buffer variable: %s"), name); + goto theend; + } } - } - - if (oplen > 0 && *op != '=') - { - type_T *expected = &t_number; - garray_T *stack = &cctx->ctx_type_stack; - type_T *stacktype; - - // TODO: if type is known use float or any operation - - if (*op == '.') - expected = &t_string; - stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (need_type(stacktype, expected, -1, cctx) == FAIL) - goto theend; - - if (*op == '.') - generate_instr_drop(cctx, ISN_CONCAT, 1); - else + else if (STRNCMP(arg, "w:", 2) == 0) { - isn_T *isn = generate_instr_drop(cctx, ISN_OPNR, 1); - - if (isn == NULL) + dest = dest_window; + if (is_decl) + { + semsg(_("E1079: Cannot declare a window variable: %s"), name); goto theend; - switch (*op) + } + } + else if (STRNCMP(arg, "t:", 2) == 0) + { + dest = dest_tab; + if (is_decl) { - case '+': isn->isn_arg.op.op_type = EXPR_ADD; break; - case '-': isn->isn_arg.op.op_type = EXPR_SUB; break; - case '*': isn->isn_arg.op.op_type = EXPR_MULT; break; - case '/': isn->isn_arg.op.op_type = EXPR_DIV; break; - case '%': isn->isn_arg.op.op_type = EXPR_REM; break; + semsg(_("E1080: Cannot declare a tab variable: %s"), name); + goto theend; } } - } + else if (STRNCMP(arg, "v:", 2) == 0) + { + typval_T *vtv; + int di_flags; - switch (dest) - { - case dest_option: - generate_STOREOPT(cctx, name + 1, opt_flags); - break; - case dest_global: - // include g: with the name, easier to execute that way - generate_STORE(cctx, ISN_STOREG, 0, name); - break; - case dest_buffer: - // include b: with the name, easier to execute that way - generate_STORE(cctx, ISN_STOREB, 0, name); - break; - case dest_window: - // include w: with the name, easier to execute that way - generate_STORE(cctx, ISN_STOREW, 0, name); - break; - case dest_tab: - // include t: with the name, easier to execute that way - generate_STORE(cctx, ISN_STORET, 0, name); - break; - case dest_env: - generate_STORE(cctx, ISN_STOREENV, 0, name + 1); - break; - case dest_reg: - generate_STORE(cctx, ISN_STOREREG, name[1], NULL); - break; - case dest_vimvar: - generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); - break; - case dest_script: + vimvaridx = find_vim_var(name + 2, &di_flags); + if (vimvaridx < 0) { - char_u *rawname = name + (name[1] == ':' ? 2 : 0); - imported_T *import = NULL; - int sid = current_sctx.sc_sid; - int idx; + semsg(_(e_var_notfound), arg); + goto theend; + } + // We use the current value of "sandbox" here, is that OK? + if (var_check_ro(di_flags, name, FALSE)) + goto theend; + dest = dest_vimvar; + vtv = get_vim_var_tv(vimvaridx); + type = typval2type(vtv); + if (is_decl) + { + semsg(_("E1064: Cannot declare a v: variable: %s"), name); + goto theend; + } + } + else + { + int idx; - if (name[1] != ':') + for (idx = 0; reserved[idx] != NULL; ++idx) + if (STRCMP(reserved[idx], name) == 0) { - import = find_imported(name, 0, cctx); - if (import != NULL) - sid = import->imp_sid; + semsg(_("E1034: Cannot use reserved name %s"), name); + goto theend; } - idx = get_script_item_idx(sid, rawname, TRUE); - // TODO: specific type - if (idx < 0) + lvar = lookup_local(arg, varlen, cctx); + if (lvar != NULL) + { + if (is_decl) { - char_u *name_s = name; - - // Include s: in the name for store_var() - if (name[1] != ':') - { - int len = (int)STRLEN(name) + 3; - - name_s = alloc(len); - if (name_s == NULL) - name_s = name; - else - vim_snprintf((char *)name_s, len, "s:%s", name); - } - generate_OLDSCRIPT(cctx, ISN_STORES, name_s, sid, &t_any); - if (name_s != name) - vim_free(name_s); + semsg(_("E1017: Variable already declared: %s"), name); + goto theend; + } + else if (lvar->lv_const) + { + semsg(_("E1018: Cannot assign to a constant: %s"), name); + goto theend; } - else - generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, - sid, idx, &t_any); } - break; - case dest_local: - if (lvar != NULL) + else if (STRNCMP(arg, "s:", 2) == 0 + || lookup_script(arg, varlen) == OK + || find_imported(arg, varlen, cctx) != NULL) { - isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; - - // optimization: turn "var = 123" from ISN_PUSHNR + ISN_STORE - // into ISN_STORENR - if (!lvar->lv_from_outer && instr->ga_len == instr_count + 1 - && isn->isn_type == ISN_PUSHNR) + dest = dest_script; + if (is_decl) { - varnumber_T val = isn->isn_arg.number; - garray_T *stack = &cctx->ctx_type_stack; - - isn->isn_type = ISN_STORENR; - isn->isn_arg.storenr.stnr_idx = lvar->lv_idx; - isn->isn_arg.storenr.stnr_val = val; - if (stack->ga_len > 0) - --stack->ga_len; + semsg(_("E1054: Variable already declared in the script: %s"), + name); + goto theend; } - else if (lvar->lv_from_outer) - generate_STORE(cctx, ISN_STOREOUTER, lvar->lv_idx, NULL); - else - generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); } - break; - } - ret = p; - -theend: - vim_free(name); - return ret; -} - -/* - * Check if "name" can be "unlet". - */ - int -check_vim9_unlet(char_u *name) -{ - if (name[1] != ':' || vim_strchr((char_u *)"gwtb", *name) == NULL) - { - semsg(_("E1081: Cannot unlet %s"), name); - return FAIL; - } - return OK; -} - -/* - * Callback passed to ex_unletlock(). - */ - static int -compile_unlet( - lval_T *lvp, - char_u *name_end, - exarg_T *eap, - int deep UNUSED, - void *coookie) -{ - cctx_T *cctx = coookie; - - if (lvp->ll_tv == NULL) - { - char_u *p = lvp->ll_name; - int cc = *name_end; - int ret = OK; - - // Normal name. Only supports g:, w:, t: and b: namespaces. - *name_end = NUL; - if (*p == '$') - ret = generate_UNLET(cctx, ISN_UNLETENV, p + 1, eap->forceit); - else if (check_vim9_unlet(p) == FAIL) - ret = FAIL; - else - ret = generate_UNLET(cctx, ISN_UNLET, p, eap->forceit); - - *name_end = cc; - return ret; + else if (name[1] == ':' && name[2] != NUL) + { + semsg(_("E1082: Cannot use a namespaced variable: %s"), name); + goto theend; + } + } } - // TODO: unlet {list}[idx] - // TODO: unlet {dict}[key] - emsg("Sorry, :unlet not fully implemented yet"); - return FAIL; -} - -/* - * compile "unlet var", "lock var" and "unlock var" - * "arg" points to "var". - */ - static char_u * -compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx) -{ - char_u *p = arg; - - if (eap->cmdidx != CMD_unlet) + if (dest != dest_option) { - emsg("Sorry, :lock and unlock not implemented yet"); - return NULL; + if (is_decl && *p == ':') + { + // parse optional type: "let var: type = expr" + if (!VIM_ISWHITE(p[1])) + { + semsg(_(e_white_after), ":"); + goto theend; + } + p = skipwhite(p + 1); + type = parse_type(&p, cctx->ctx_type_list); + has_type = TRUE; + } + else if (lvar != NULL) + type = lvar->lv_type; } - if (*p == '!') + sp = p; + p = skipwhite(p); + op = p; + oplen = assignment_len(p, &heredoc); + if (oplen > 0 && (!VIM_ISWHITE(*sp) || !VIM_ISWHITE(op[oplen]))) { - p = skipwhite(p + 1); - eap->forceit = TRUE; - } - - ex_unletlock(eap, p, 0, GLV_NO_AUTOLOAD, compile_unlet, cctx); - return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd; -} - -/* - * Compile an :import command. - */ - static char_u * -compile_import(char_u *arg, cctx_T *cctx) -{ - return handle_import(arg, &cctx->ctx_imports, 0, cctx); -} - -/* - * generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry". - */ - static int -compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx) -{ - garray_T *instr = &cctx->ctx_instr; - endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T); - - if (endlabel == NULL) - return FAIL; - endlabel->el_next = *el; - *el = endlabel; - endlabel->el_end_label = instr->ga_len; - - generate_JUMP(cctx, when, 0); - return OK; -} + char_u buf[4]; - static void -compile_fill_jump_to_end(endlabel_T **el, cctx_T *cctx) -{ - garray_T *instr = &cctx->ctx_instr; + vim_strncpy(buf, op, oplen); + semsg(_(e_white_both), buf); + } - while (*el != NULL) + if (oplen == 3 && !heredoc && dest != dest_global + && type->tt_type != VAR_STRING && type->tt_type != VAR_ANY) { - endlabel_T *cur = (*el); - isn_T *isn; - - isn = ((isn_T *)instr->ga_data) + cur->el_end_label; - isn->isn_arg.jump.jump_where = instr->ga_len; - *el = cur->el_next; - vim_free(cur); + emsg(_("E1019: Can only concatenate to string")); + goto theend; } -} - static void -compile_free_jump_to_end(endlabel_T **el) -{ - while (*el != NULL) + if (lvar == NULL && dest == dest_local && cctx->ctx_skip != TRUE) { - endlabel_T *cur = (*el); + if (oplen > 1 && !heredoc) + { + // +=, /=, etc. require an existing variable + semsg(_("E1020: cannot use an operator on a new variable: %s"), + name); + goto theend; + } - *el = cur->el_next; - vim_free(cur); + // new local variable + if (type->tt_type == VAR_FUNC && var_check_func_name(name, TRUE)) + goto theend; + lvar = reserve_local(cctx, arg, varlen, cmdidx == CMD_const, type); + if (lvar == NULL) + goto theend; + new_local = TRUE; } -} - -/* - * Create a new scope and set up the generic items. - */ - static scope_T * -new_scope(cctx_T *cctx, scopetype_T type) -{ - scope_T *scope = ALLOC_CLEAR_ONE(scope_T); - if (scope == NULL) - return NULL; - scope->se_outer = cctx->ctx_scope; - cctx->ctx_scope = scope; - scope->se_type = type; - scope->se_local_count = cctx->ctx_locals.ga_len; - return scope; -} + if (heredoc) + { + list_T *l; + listitem_T *li; -/* - * Free the current scope and go back to the outer scope. - */ - static void -drop_scope(cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; + // [let] varname =<< [trim] {end} + eap->getline = exarg_getline; + eap->cookie = cctx; + l = heredoc_get(eap, op + 3, FALSE); - if (scope == NULL) - { - iemsg("calling drop_scope() without a scope"); - return; + // Push each line and the create the list. + FOR_ALL_LIST_ITEMS(l, li) + { + generate_PUSHS(cctx, li->li_tv.vval.v_string); + li->li_tv.vval.v_string = NULL; + } + generate_NEWLIST(cctx, l->lv_len); + type = &t_list_string; + list_free(l); + p += STRLEN(p); } - cctx->ctx_scope = scope->se_outer; - switch (scope->se_type) + else if (oplen > 0) { - case IF_SCOPE: - compile_free_jump_to_end(&scope->se_u.se_if.is_end_label); break; - case FOR_SCOPE: - compile_free_jump_to_end(&scope->se_u.se_for.fs_end_label); break; - case WHILE_SCOPE: - compile_free_jump_to_end(&scope->se_u.se_while.ws_end_label); break; - case TRY_SCOPE: - compile_free_jump_to_end(&scope->se_u.se_try.ts_end_label); break; - case NO_SCOPE: - case BLOCK_SCOPE: - break; - } - vim_free(scope); -} + int r; + type_T *stacktype; + garray_T *stack; -/* - * Evaluate an expression that is a constant: - * has(arg) - * - * Also handle: - * ! in front logical NOT - * - * Return FAIL if the expression is not a constant. - */ - static int -evaluate_const_expr7(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) -{ - typval_T argvars[2]; - char_u *start_leader, *end_leader; - int has_call = FALSE; + // for "+=", "*=", "..=" etc. first load the current value + if (*op != '=') + { + switch (dest) + { + case dest_option: + // TODO: check the option exists + generate_LOAD(cctx, ISN_LOADOPT, 0, name, type); + break; + case dest_global: + generate_LOAD(cctx, ISN_LOADG, 0, name + 2, type); + break; + case dest_buffer: + generate_LOAD(cctx, ISN_LOADB, 0, name + 2, type); + break; + case dest_window: + generate_LOAD(cctx, ISN_LOADW, 0, name + 2, type); + break; + case dest_tab: + generate_LOAD(cctx, ISN_LOADT, 0, name + 2, type); + break; + case dest_script: + compile_load_scriptvar(cctx, + name + (name[1] == ':' ? 2 : 0), NULL, NULL, TRUE); + break; + case dest_env: + // Include $ in the name here + generate_LOAD(cctx, ISN_LOADENV, 0, name, type); + break; + case dest_reg: + generate_LOAD(cctx, ISN_LOADREG, arg[1], NULL, &t_string); + break; + case dest_vimvar: + generate_LOADV(cctx, name + 2, TRUE); + break; + case dest_local: + if (lvar->lv_from_outer) + generate_LOAD(cctx, ISN_LOADOUTER, lvar->lv_idx, + NULL, type); + else + generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); + break; + } + } - /* - * Skip '!' characters. They are handled later. - */ - start_leader = *arg; - while (**arg == '!') - *arg = skipwhite(*arg + 1); - end_leader = *arg; + // Compile the expression. Temporarily hide the new local variable + // here, it is not available to this expression. + if (new_local) + --cctx->ctx_locals.ga_len; + instr_count = instr->ga_len; + p = skipwhite(p + oplen); + r = compile_expr1(&p, cctx); + if (new_local) + ++cctx->ctx_locals.ga_len; + if (r == FAIL) + goto theend; - /* - * Recognize only a few types of constants for now. - */ - if (STRNCMP("true", *arg, 4) == 0 && !ASCII_ISALNUM((*arg)[4])) - { - tv->v_type = VAR_SPECIAL; - tv->vval.v_number = VVAL_TRUE; - *arg += 4; - return OK; - } - if (STRNCMP("false", *arg, 5) == 0 && !ASCII_ISALNUM((*arg)[5])) - { - tv->v_type = VAR_SPECIAL; - tv->vval.v_number = VVAL_FALSE; - *arg += 5; - return OK; + if (cctx->ctx_skip != TRUE) + { + stack = &cctx->ctx_type_stack; + stacktype = stack->ga_len == 0 ? &t_void + : ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (lvar != NULL && (is_decl || !has_type)) + { + if (new_local && !has_type) + { + if (stacktype->tt_type == VAR_VOID) + { + emsg(_("E1031: Cannot use void value")); + goto theend; + } + else + { + // An empty list or dict has a &t_void member, for a + // variable that implies &t_any. + if (stacktype == &t_list_empty) + lvar->lv_type = &t_list_any; + else if (stacktype == &t_dict_empty) + lvar->lv_type = &t_dict_any; + else + lvar->lv_type = stacktype; + } + } + else if (need_type(stacktype, lvar->lv_type, -1, cctx) == FAIL) + goto theend; + } + else if (*p != '=' && check_type(type, stacktype, TRUE) == FAIL) + goto theend; + } } - - if (STRNCMP("has(", *arg, 4) == 0) + else if (cmdidx == CMD_const) { - has_call = TRUE; - *arg = skipwhite(*arg + 4); + emsg(_("E1021: const requires a value")); + goto theend; } - - if (**arg == '"') + else if (!has_type || dest == dest_option) { - if (get_string_tv(arg, tv, TRUE) == FAIL) - return FAIL; + emsg(_("E1022: type or initialization required")); + goto theend; } - else if (**arg == '\'') + else { - if (get_lit_string_tv(arg, tv, TRUE) == FAIL) - return FAIL; + // variables are always initialized + if (ga_grow(instr, 1) == FAIL) + goto theend; + switch (type->tt_type) + { + case VAR_BOOL: + generate_PUSHBOOL(cctx, VVAL_FALSE); + break; + case VAR_FLOAT: +#ifdef FEAT_FLOAT + generate_PUSHF(cctx, 0.0); +#endif + break; + case VAR_STRING: + generate_PUSHS(cctx, NULL); + break; + case VAR_BLOB: + generate_PUSHBLOB(cctx, NULL); + break; + case VAR_FUNC: + generate_PUSHFUNC(cctx, NULL, &t_func_void); + break; + case VAR_LIST: + generate_NEWLIST(cctx, 0); + break; + case VAR_DICT: + generate_NEWDICT(cctx, 0); + break; + case VAR_JOB: + generate_PUSHJOB(cctx, NULL); + break; + case VAR_CHANNEL: + generate_PUSHCHANNEL(cctx, NULL); + break; + case VAR_NUMBER: + case VAR_UNKNOWN: + case VAR_ANY: + case VAR_PARTIAL: + case VAR_VOID: + case VAR_SPECIAL: // cannot happen + generate_PUSHNR(cctx, 0); + break; + } } - else - return FAIL; - if (has_call) + if (oplen > 0 && *op != '=') { - *arg = skipwhite(*arg); - if (**arg != ')') - return FAIL; - *arg = *arg + 1; + type_T *expected = &t_number; + garray_T *stack = &cctx->ctx_type_stack; + type_T *stacktype; - argvars[0] = *tv; - argvars[1].v_type = VAR_UNKNOWN; - tv->v_type = VAR_NUMBER; - tv->vval.v_number = 0; - f_has(argvars, tv); - clear_tv(&argvars[0]); + // TODO: if type is known use float or any operation - while (start_leader < end_leader) + if (*op == '.') + expected = &t_string; + stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (need_type(stacktype, expected, -1, cctx) == FAIL) + goto theend; + + if (*op == '.') + generate_instr_drop(cctx, ISN_CONCAT, 1); + else { - if (*start_leader == '!') - tv->vval.v_number = !tv->vval.v_number; - ++start_leader; + isn_T *isn = generate_instr_drop(cctx, ISN_OPNR, 1); + + if (isn == NULL) + goto theend; + switch (*op) + { + case '+': isn->isn_arg.op.op_type = EXPR_ADD; break; + case '-': isn->isn_arg.op.op_type = EXPR_SUB; break; + case '*': isn->isn_arg.op.op_type = EXPR_MULT; break; + case '/': isn->isn_arg.op.op_type = EXPR_DIV; break; + case '%': isn->isn_arg.op.op_type = EXPR_REM; break; + } } } - return OK; -} + switch (dest) + { + case dest_option: + generate_STOREOPT(cctx, name + 1, opt_flags); + break; + case dest_global: + // include g: with the name, easier to execute that way + generate_STORE(cctx, ISN_STOREG, 0, name); + break; + case dest_buffer: + // include b: with the name, easier to execute that way + generate_STORE(cctx, ISN_STOREB, 0, name); + break; + case dest_window: + // include w: with the name, easier to execute that way + generate_STORE(cctx, ISN_STOREW, 0, name); + break; + case dest_tab: + // include t: with the name, easier to execute that way + generate_STORE(cctx, ISN_STORET, 0, name); + break; + case dest_env: + generate_STORE(cctx, ISN_STOREENV, 0, name + 1); + break; + case dest_reg: + generate_STORE(cctx, ISN_STOREREG, name[1], NULL); + break; + case dest_vimvar: + generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); + break; + case dest_script: + { + char_u *rawname = name + (name[1] == ':' ? 2 : 0); + imported_T *import = NULL; + int sid = current_sctx.sc_sid; + int idx; - static int -evaluate_const_expr4(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) -{ - exptype_T type = EXPR_UNKNOWN; - char_u *p; - int len = 2; - int type_is = FALSE; + if (name[1] != ':') + { + import = find_imported(name, 0, cctx); + if (import != NULL) + sid = import->imp_sid; + } - // get the first variable - if (evaluate_const_expr7(arg, cctx, tv) == FAIL) - return FAIL; + idx = get_script_item_idx(sid, rawname, TRUE); + // TODO: specific type + if (idx < 0) + { + char_u *name_s = name; - p = skipwhite(*arg); - type = get_compare_type(p, &len, &type_is); + // Include s: in the name for store_var() + if (name[1] != ':') + { + int len = (int)STRLEN(name) + 3; - /* - * If there is a comparative operator, use it. - */ - if (type != EXPR_UNKNOWN) - { - typval_T tv2; - char_u *s1, *s2; - char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; - int n; + name_s = alloc(len); + if (name_s == NULL) + name_s = name; + else + vim_snprintf((char *)name_s, len, "s:%s", name); + } + generate_OLDSCRIPT(cctx, ISN_STORES, name_s, sid, &t_any); + if (name_s != name) + vim_free(name_s); + } + else + generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, + sid, idx, &t_any); + } + break; + case dest_local: + if (lvar != NULL) + { + isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; - // TODO: Only string == string is supported now - if (tv->v_type != VAR_STRING) - return FAIL; - if (type != EXPR_EQUAL) - return FAIL; + // optimization: turn "var = 123" from ISN_PUSHNR + ISN_STORE + // into ISN_STORENR + if (!lvar->lv_from_outer && instr->ga_len == instr_count + 1 + && isn->isn_type == ISN_PUSHNR) + { + varnumber_T val = isn->isn_arg.number; + garray_T *stack = &cctx->ctx_type_stack; - // get the second variable - init_tv(&tv2); - *arg = skipwhite(p + len); - if (evaluate_const_expr7(arg, cctx, &tv2) == FAIL - || tv2.v_type != VAR_STRING) - { - clear_tv(&tv2); - return FAIL; - } - s1 = tv_get_string_buf(tv, buf1); - s2 = tv_get_string_buf(&tv2, buf2); - n = STRCMP(s1, s2); - clear_tv(tv); - clear_tv(&tv2); - tv->v_type = VAR_BOOL; - tv->vval.v_number = n == 0 ? VVAL_TRUE : VVAL_FALSE; + isn->isn_type = ISN_STORENR; + isn->isn_arg.storenr.stnr_idx = lvar->lv_idx; + isn->isn_arg.storenr.stnr_val = val; + if (stack->ga_len > 0) + --stack->ga_len; + } + else if (lvar->lv_from_outer) + generate_STORE(cctx, ISN_STOREOUTER, lvar->lv_idx, NULL); + else + generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); + } + break; } + ret = p; - return OK; +theend: + vim_free(name); + return ret; } -static int evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv); +/* + * Check if "name" can be "unlet". + */ + int +check_vim9_unlet(char_u *name) +{ + if (name[1] != ':' || vim_strchr((char_u *)"gwtb", *name) == NULL) + { + semsg(_("E1081: Cannot unlet %s"), name); + return FAIL; + } + return OK; +} /* - * Compile constant || or &&. + * Callback passed to ex_unletlock(). */ static int -evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv) +compile_unlet( + lval_T *lvp, + char_u *name_end, + exarg_T *eap, + int deep UNUSED, + void *coookie) { - char_u *p = skipwhite(*arg); - int opchar = *op; + cctx_T *cctx = coookie; - if (p[0] == opchar && p[1] == opchar) + if (lvp->ll_tv == NULL) { - int val = tv2bool(tv); - - /* - * Repeat until there is no following "||" or "&&" - */ - while (p[0] == opchar && p[1] == opchar) - { - typval_T tv2; + char_u *p = lvp->ll_name; + int cc = *name_end; + int ret = OK; - if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2])) - return FAIL; + // Normal name. Only supports g:, w:, t: and b: namespaces. + *name_end = NUL; + if (*p == '$') + ret = generate_UNLET(cctx, ISN_UNLETENV, p + 1, eap->forceit); + else if (check_vim9_unlet(p) == FAIL) + ret = FAIL; + else + ret = generate_UNLET(cctx, ISN_UNLET, p, eap->forceit); - // eval the next expression - *arg = skipwhite(p + 2); - tv2.v_type = VAR_UNKNOWN; - tv2.v_lock = 0; - if ((opchar == '|' ? evaluate_const_expr3(arg, cctx, &tv2) - : evaluate_const_expr4(arg, cctx, &tv2)) == FAIL) - { - clear_tv(&tv2); - return FAIL; - } - if ((opchar == '&') == val) - { - // false || tv2 or true && tv2: use tv2 - clear_tv(tv); - *tv = tv2; - val = tv2bool(tv); - } - else - clear_tv(&tv2); - p = skipwhite(*arg); - } + *name_end = cc; + return ret; } - return OK; + // TODO: unlet {list}[idx] + // TODO: unlet {dict}[key] + emsg("Sorry, :unlet not fully implemented yet"); + return FAIL; } /* - * Evaluate an expression that is a constant: expr4 && expr4 && expr4 - * Return FAIL if the expression is not a constant. + * compile "unlet var", "lock var" and "unlock var" + * "arg" points to "var". */ - static int -evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv) + static char_u * +compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx) { - // evaluate the first expression - if (evaluate_const_expr4(arg, cctx, tv) == FAIL) - return FAIL; + char_u *p = arg; - // || and && work almost the same - return evaluate_const_and_or(arg, cctx, "&&", tv); + if (eap->cmdidx != CMD_unlet) + { + emsg("Sorry, :lock and unlock not implemented yet"); + return NULL; + } + + if (*p == '!') + { + p = skipwhite(p + 1); + eap->forceit = TRUE; + } + + ex_unletlock(eap, p, 0, GLV_NO_AUTOLOAD, compile_unlet, cctx); + return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd; } /* - * Evaluate an expression that is a constant: expr3 || expr3 || expr3 - * Return FAIL if the expression is not a constant. + * Compile an :import command. */ - static int -evaluate_const_expr2(char_u **arg, cctx_T *cctx, typval_T *tv) + static char_u * +compile_import(char_u *arg, cctx_T *cctx) { - // evaluate the first expression - if (evaluate_const_expr3(arg, cctx, tv) == FAIL) - return FAIL; - - // || and && work almost the same - return evaluate_const_and_or(arg, cctx, "||", tv); + return handle_import(arg, &cctx->ctx_imports, 0, cctx); } /* - * Evaluate an expression that is a constant: expr2 ? expr1 : expr1 - * E.g. for "has('feature')". - * This does not produce error messages. "tv" should be cleared afterwards. - * Return FAIL if the expression is not a constant. + * generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry". */ static int -evaluate_const_expr1(char_u **arg, cctx_T *cctx, typval_T *tv) +compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx) { - char_u *p; + garray_T *instr = &cctx->ctx_instr; + endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T); - // evaluate the first expression - if (evaluate_const_expr2(arg, cctx, tv) == FAIL) + if (endlabel == NULL) return FAIL; + endlabel->el_next = *el; + *el = endlabel; + endlabel->el_end_label = instr->ga_len; - p = skipwhite(*arg); - if (*p == '?') + generate_JUMP(cctx, when, 0); + return OK; +} + + static void +compile_fill_jump_to_end(endlabel_T **el, cctx_T *cctx) +{ + garray_T *instr = &cctx->ctx_instr; + + while (*el != NULL) { - int val = tv2bool(tv); - typval_T tv2; + endlabel_T *cur = (*el); + isn_T *isn; - // require space before and after the ? - if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) - return FAIL; + isn = ((isn_T *)instr->ga_data) + cur->el_end_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + *el = cur->el_next; + vim_free(cur); + } +} - // evaluate the second expression; any type is accepted - clear_tv(tv); - *arg = skipwhite(p + 1); - if (evaluate_const_expr1(arg, cctx, tv) == FAIL) - return FAIL; + static void +compile_free_jump_to_end(endlabel_T **el) +{ + while (*el != NULL) + { + endlabel_T *cur = (*el); - // Check for the ":". - p = skipwhite(*arg); - if (*p != ':' || !VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) - return FAIL; + *el = cur->el_next; + vim_free(cur); + } +} - // evaluate the third expression - *arg = skipwhite(p + 1); - tv2.v_type = VAR_UNKNOWN; - if (evaluate_const_expr1(arg, cctx, &tv2) == FAIL) - { - clear_tv(&tv2); - return FAIL; - } - if (val) - { - // use the expr after "?" - clear_tv(&tv2); - } - else - { - // use the expr after ":" - clear_tv(tv); - *tv = tv2; - } +/* + * Create a new scope and set up the generic items. + */ + static scope_T * +new_scope(cctx_T *cctx, scopetype_T type) +{ + scope_T *scope = ALLOC_CLEAR_ONE(scope_T); + + if (scope == NULL) + return NULL; + scope->se_outer = cctx->ctx_scope; + cctx->ctx_scope = scope; + scope->se_type = type; + scope->se_local_count = cctx->ctx_locals.ga_len; + return scope; +} + +/* + * Free the current scope and go back to the outer scope. + */ + static void +drop_scope(cctx_T *cctx) +{ + scope_T *scope = cctx->ctx_scope; + + if (scope == NULL) + { + iemsg("calling drop_scope() without a scope"); + return; } - return OK; + cctx->ctx_scope = scope->se_outer; + switch (scope->se_type) + { + case IF_SCOPE: + compile_free_jump_to_end(&scope->se_u.se_if.is_end_label); break; + case FOR_SCOPE: + compile_free_jump_to_end(&scope->se_u.se_for.fs_end_label); break; + case WHILE_SCOPE: + compile_free_jump_to_end(&scope->se_u.se_while.ws_end_label); break; + case TRY_SCOPE: + compile_free_jump_to_end(&scope->se_u.se_try.ts_end_label); break; + case NO_SCOPE: + case BLOCK_SCOPE: + break; + } + vim_free(scope); } /*