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.
*/
}
/*
- * 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,
}
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);
}
/*