diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 813b79bae9..cb80bd05c6 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -89,7 +89,9 @@ CONFIG = { # Section ordering. 'section_order': [ 'vim.c', + 'vimscript.c', 'buffer.c', + 'extmark.c', 'window.c', 'win_config.c', 'tabpage.c', diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 815dd70a52..76b699800e 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -12,6 +12,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" +#include "nvim/api/vimscript.h" #include "nvim/extmark.h" #include "nvim/lua/executor.h" diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index 79af9bf176..8ab7743e01 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -15,6 +15,7 @@ #include "nvim/api/tabpage.h" #include "nvim/api/ui.h" #include "nvim/api/vim.h" +#include "nvim/api/vimscript.h" #include "nvim/api/win_config.h" #include "nvim/api/window.h" #include "nvim/log.h" diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index cbaba72213..3ca6189ddd 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -60,84 +60,6 @@ # include "api/vim.c.generated.h" #endif -/// Executes Vimscript (multiline block of Ex-commands), like anonymous -/// |:source|. -/// -/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:), -/// etc. -/// -/// On execution error: fails with VimL error, does not update v:errmsg. -/// -/// @see |execute()| -/// @see |nvim_command()| -/// -/// @param src Vimscript code -/// @param output Capture and return all (non-error, non-shell |:!|) output -/// @param[out] err Error details (Vim error), if any -/// @return Output (non-error, non-shell |:!|) if `output` is true, -/// else empty string. -String nvim_exec(String src, Boolean output, Error *err) - FUNC_API_SINCE(7) -{ - const int save_msg_silent = msg_silent; - garray_T *const save_capture_ga = capture_ga; - garray_T capture_local; - if (output) { - ga_init(&capture_local, 1, 80); - capture_ga = &capture_local; - } - - try_start(); - if (output) { - msg_silent++; - } - do_source_str(src.data, "nvim_exec()"); - if (output) { - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; - } - try_end(err); - - if (ERROR_SET(err)) { - goto theend; - } - - if (output && capture_local.ga_len > 1) { - String s = (String){ - .data = capture_local.ga_data, - .size = (size_t)capture_local.ga_len, - }; - // redir usually (except :echon) prepends a newline. - if (s.data[0] == '\n') { - memmove(s.data, s.data + 1, s.size - 1); - s.data[s.size - 1] = '\0'; - s.size = s.size - 1; - } - return s; // Caller will free the memory. - } -theend: - if (output) { - ga_clear(&capture_local); - } - return (String)STRING_INIT; -} - -/// Executes an ex-command. -/// -/// On execution error: fails with VimL error, does not update v:errmsg. -/// -/// @see |nvim_exec()| -/// -/// @param command Ex-command string -/// @param[out] err Error details (Vim error), if any -void nvim_command(String command, Error *err) - FUNC_API_SINCE(1) -{ - try_start(); - do_cmdline_cmd(command.data); - try_end(err); -} - /// Gets a highlight definition by name. /// /// @param name Highlight group name @@ -478,51 +400,6 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool return cstr_as_string(ptr); } -/// Evaluates a VimL |expression|. -/// Dictionaries and Lists are recursively expanded. -/// -/// On execution error: fails with VimL error, does not update v:errmsg. -/// -/// @param expr VimL expression string -/// @param[out] err Error details, if any -/// @return Evaluation result or expanded object -Object nvim_eval(String expr, Error *err) - FUNC_API_SINCE(1) -{ - static int recursive = 0; // recursion depth - Object rv = OBJECT_INIT; - - TRY_WRAP({ - // Initialize `force_abort` and `suppress_errthrow` at the top level. - if (!recursive) { - force_abort = false; - suppress_errthrow = false; - current_exception = NULL; - // `did_emsg` is set by emsg(), which cancels execution. - did_emsg = false; - } - recursive++; - try_start(); - - typval_T rettv; - int ok = eval0((char_u *)expr.data, &rettv, NULL, true); - - if (!try_end(err)) { - if (ok == FAIL) { - // Should never happen, try_end() should get the error. #8371 - api_set_error(err, kErrorTypeException, - "Failed to evaluate expression: '%.*s'", 256, expr.data); - } else { - rv = vim_to_object(&rettv); - } - } - - tv_clear(&rettv); - recursive--; - }); - - return rv; -} /// Execute Lua code. Parameters (if any) are available as `...` inside the /// chunk. The chunk can return a value. @@ -563,164 +440,6 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err) return nlua_exec(STATIC_CSTR_AS_STRING("return vim.notify(...)"), args, err); } -/// Calls a VimL function. -/// -/// @param fn Function name -/// @param args Function arguments -/// @param self `self` dict, or NULL for non-dict functions -/// @param[out] err Error details, if any -/// @return Result of the function call -static Object _call_function(String fn, Array args, dict_T *self, Error *err) -{ - static int recursive = 0; // recursion depth - Object rv = OBJECT_INIT; - - if (args.size > MAX_FUNC_ARGS) { - api_set_error(err, kErrorTypeValidation, - "Function called with too many arguments"); - return rv; - } - - // Convert the arguments in args from Object to typval_T values - typval_T vim_args[MAX_FUNC_ARGS + 1]; - size_t i = 0; // also used for freeing the variables - for (; i < args.size; i++) { - if (!object_to_vim(args.items[i], &vim_args[i], err)) { - goto free_vim_args; - } - } - - TRY_WRAP({ - // Initialize `force_abort` and `suppress_errthrow` at the top level. - if (!recursive) { - force_abort = false; - suppress_errthrow = false; - current_exception = NULL; - // `did_emsg` is set by emsg(), which cancels execution. - did_emsg = false; - } - recursive++; - try_start(); - typval_T rettv; - funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = true; - funcexe.selfdict = self; - // call_func() retval is deceptive, ignore it. Instead we set `msg_list` - // (see above) to capture abort-causing non-exception errors. - (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, - vim_args, &funcexe); - if (!try_end(err)) { - rv = vim_to_object(&rettv); - } - tv_clear(&rettv); - recursive--; - }); - -free_vim_args: - while (i > 0) { - tv_clear(&vim_args[--i]); - } - - return rv; -} - -/// Calls a VimL function with the given arguments. -/// -/// On execution error: fails with VimL error, does not update v:errmsg. -/// -/// @param fn Function to call -/// @param args Function arguments packed in an Array -/// @param[out] err Error details, if any -/// @return Result of the function call -Object nvim_call_function(String fn, Array args, Error *err) - FUNC_API_SINCE(1) -{ - return _call_function(fn, args, NULL, err); -} - -/// Calls a VimL |Dictionary-function| with the given arguments. -/// -/// On execution error: fails with VimL error, does not update v:errmsg. -/// -/// @param dict Dictionary, or String evaluating to a VimL |self| dict -/// @param fn Name of the function defined on the VimL dict -/// @param args Function arguments packed in an Array -/// @param[out] err Error details, if any -/// @return Result of the function call -Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) - FUNC_API_SINCE(4) -{ - Object rv = OBJECT_INIT; - - typval_T rettv; - bool mustfree = false; - switch (dict.type) { - case kObjectTypeString: - try_start(); - if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) { - api_set_error(err, kErrorTypeException, - "Failed to evaluate dict expression"); - } - if (try_end(err)) { - return rv; - } - // Evaluation of the string arg created a new dict or increased the - // refcount of a dict. Not necessary for a RPC dict. - mustfree = true; - break; - case kObjectTypeDictionary: - if (!object_to_vim(dict, &rettv, err)) { - goto end; - } - break; - default: - api_set_error(err, kErrorTypeValidation, - "dict argument type must be String or Dictionary"); - return rv; - } - dict_T *self_dict = rettv.vval.v_dict; - if (rettv.v_type != VAR_DICT || !self_dict) { - api_set_error(err, kErrorTypeValidation, "dict not found"); - goto end; - } - - if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) { - dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); - if (di == NULL) { - api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data); - goto end; - } - if (di->di_tv.v_type == VAR_PARTIAL) { - api_set_error(err, kErrorTypeValidation, - "partial function not supported"); - goto end; - } - if (di->di_tv.v_type != VAR_FUNC) { - api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data); - goto end; - } - fn = (String) { - .data = (char *)di->di_tv.vval.v_string, - .size = STRLEN(di->di_tv.vval.v_string), - }; - } - - if (!fn.data || fn.size < 1) { - api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name"); - goto end; - } - - rv = _call_function(fn, args, self_dict, err); -end: - if (mustfree) { - tv_clear(&rettv); - } - - return rv; -} - /// Calculates the number of display cells occupied by `text`. /// counts as one cell. /// @@ -1991,439 +1710,6 @@ theend: return rv; } -typedef struct { - ExprASTNode **node_p; - Object *ret_node_p; -} ExprASTConvStackItem; - -/// @cond DOXYGEN_NOT_A_FUNCTION -typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; -/// @endcond - -/// Parse a VimL expression. -/// -/// @param[in] expr Expression to parse. Always treated as a single line. -/// @param[in] flags Flags: -/// - "m" if multiple expressions in a row are allowed (only -/// the first one will be parsed), -/// - "E" if EOC tokens are not allowed (determines whether -/// they will stop parsing process or be recognized as an -/// operator/space, though also yielding an error). -/// - "l" when needing to start parsing with lvalues for -/// ":let" or ":for". -/// Common flag sets: -/// - "m" to parse like for ":echo". -/// - "E" to parse like for "=". -/// - empty string for ":call". -/// - "lm" to parse for ":let". -/// @param[in] highlight If true, return value will also include "highlight" -/// key containing array of 4-tuples (arrays) (Integer, -/// Integer, Integer, String), where first three numbers -/// define the highlighted region and represent line, -/// starting column and ending column (latter exclusive: -/// one should highlight region [start_col, end_col)). -/// -/// @return -/// - AST: top-level dictionary with these keys: -/// - "error": Dictionary with error, present only if parser saw some -/// error. Contains the following keys: -/// - "message": String, error message in printf format, translated. -/// Must contain exactly one "%.*s". -/// - "arg": String, error message argument. -/// - "len": Amount of bytes successfully parsed. With flags equal to "" -/// that should be equal to the length of expr string. -/// (“Successfully parsed” here means “participated in AST -/// creation”, not “till the first error”.) -/// - "ast": AST, either nil or a dictionary with these keys: -/// - "type": node type, one of the value names from ExprASTNodeType -/// stringified without "kExprNode" prefix. -/// - "start": a pair [line, column] describing where node is "started" -/// where "line" is always 0 (will not be 0 if you will be -/// using nvim_parse_viml() on e.g. ":let", but that is not -/// present yet). Both elements are Integers. -/// - "len": “length” of the node. This and "start" are there for -/// debugging purposes primary (debugging parser and providing -/// debug information). -/// - "children": a list of nodes described in top/"ast". There always -/// is zero, one or two children, key will not be present -/// if node has no children. Maximum number of children -/// may be found in node_maxchildren array. -/// - Local values (present only for certain nodes): -/// - "scope": a single Integer, specifies scope for "Option" and -/// "PlainIdentifier" nodes. For "Option" it is one of -/// ExprOptScope values, for "PlainIdentifier" it is one of -/// ExprVarScope values. -/// - "ident": identifier (without scope, if any), present for "Option", -/// "PlainIdentifier", "PlainKey" and "Environment" nodes. -/// - "name": Integer, register name (one character) or -1. Only present -/// for "Register" nodes. -/// - "cmp_type": String, comparison type, one of the value names from -/// ExprComparisonType, stringified without "kExprCmp" -/// prefix. Only present for "Comparison" nodes. -/// - "ccs_strategy": String, case comparison strategy, one of the -/// value names from ExprCaseCompareStrategy, -/// stringified without "kCCStrategy" prefix. Only -/// present for "Comparison" nodes. -/// - "augmentation": String, augmentation type for "Assignment" nodes. -/// Is either an empty string, "Add", "Subtract" or -/// "Concat" for "=", "+=", "-=" or ".=" respectively. -/// - "invert": Boolean, true if result of comparison needs to be -/// inverted. Only present for "Comparison" nodes. -/// - "ivalue": Integer, integer value for "Integer" nodes. -/// - "fvalue": Float, floating-point value for "Float" nodes. -/// - "svalue": String, value for "SingleQuotedString" and -/// "DoubleQuotedString" nodes. -/// @param[out] err Error details, if any -Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err) - FUNC_API_SINCE(4) FUNC_API_FAST -{ - int pflags = 0; - for (size_t i = 0 ; i < flags.size ; i++) { - switch (flags.data[i]) { - case 'm': - pflags |= kExprFlagsMulti; break; - case 'E': - pflags |= kExprFlagsDisallowEOC; break; - case 'l': - pflags |= kExprFlagsParseLet; break; - case NUL: - api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", - (unsigned)flags.data[i]); - return (Dictionary)ARRAY_DICT_INIT; - default: - api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", - flags.data[i], (unsigned)flags.data[i]); - return (Dictionary)ARRAY_DICT_INIT; - } - } - ParserLine parser_lines[] = { - { - .data = expr.data, - .size = expr.size, - .allocated = false, - }, - { NULL, 0, false }, - }; - ParserLine *plines_p = parser_lines; - ParserHighlight colors; - kvi_init(colors); - ParserHighlight *const colors_p = (highlight ? &colors : NULL); - ParserState pstate; - viml_parser_init(&pstate, parser_simple_get_line, &plines_p, colors_p); - ExprAST east = viml_pexpr_parse(&pstate, pflags); - - const size_t ret_size = ( - 2 // "ast", "len" - + (size_t)(east.err.msg != NULL) // "error" - + (size_t)highlight // "highlight" - + 0); - Dictionary ret = { - .items = xmalloc(ret_size * sizeof(ret.items[0])), - .size = 0, - .capacity = ret_size, - }; - ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("ast"), - .value = NIL, - }; - ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("len"), - .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 - ? parser_lines[0].size - : pstate.pos.col)), - }; - if (east.err.msg != NULL) { - Dictionary err_dict = { - .items = xmalloc(2 * sizeof(err_dict.items[0])), - .size = 2, - .capacity = 2, - }; - err_dict.items[0] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("message"), - .value = STRING_OBJ(cstr_to_string(east.err.msg)), - }; - if (east.err.arg == NULL) { - err_dict.items[1] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("arg"), - .value = STRING_OBJ(STRING_INIT), - }; - } else { - err_dict.items[1] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("arg"), - .value = STRING_OBJ(((String) { - .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), - .size = (size_t)east.err.arg_len, - })), - }; - } - ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("error"), - .value = DICTIONARY_OBJ(err_dict), - }; - } - if (highlight) { - Array hl = (Array) { - .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), - .capacity = kv_size(colors), - .size = kv_size(colors), - }; - for (size_t i = 0 ; i < kv_size(colors) ; i++) { - const ParserHighlightChunk chunk = kv_A(colors, i); - Array chunk_arr = (Array) { - .items = xmalloc(4 * sizeof(chunk_arr.items[0])), - .capacity = 4, - .size = 4, - }; - chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); - chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); - chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); - chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); - hl.items[i] = ARRAY_OBJ(chunk_arr); - } - ret.items[ret.size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("highlight"), - .value = ARRAY_OBJ(hl), - }; - } - kvi_destroy(colors); - - // Walk over the AST, freeing nodes in process. - ExprASTConvStack ast_conv_stack; - kvi_init(ast_conv_stack); - kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { - .node_p = &east.root, - .ret_node_p = &ret.items[0].value, - })); - while (kv_size(ast_conv_stack)) { - ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); - ExprASTNode *const node = *cur_item.node_p; - if (node == NULL) { - assert(kv_size(ast_conv_stack) == 1); - kv_drop(ast_conv_stack, 1); - } else { - if (cur_item.ret_node_p->type == kObjectTypeNil) { - const size_t ret_node_items_size = (size_t)( - 3 // "type", "start" and "len" - + (node->children != NULL) // "children" - + (node->type == kExprNodeOption - || node->type == kExprNodePlainIdentifier) // "scope" - + (node->type == kExprNodeOption - || node->type == kExprNodePlainIdentifier - || node->type == kExprNodePlainKey - || node->type == kExprNodeEnvironment) // "ident" - + (node->type == kExprNodeRegister) // "name" - + (3 // "cmp_type", "ccs_strategy", "invert" - * (node->type == kExprNodeComparison)) - + (node->type == kExprNodeInteger) // "ivalue" - + (node->type == kExprNodeFloat) // "fvalue" - + (node->type == kExprNodeDoubleQuotedString - || node->type == kExprNodeSingleQuotedString) // "svalue" - + (node->type == kExprNodeAssignment) // "augmentation" - + 0); - Dictionary ret_node = { - .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), - .capacity = ret_node_items_size, - .size = 0, - }; - *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); - } - Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; - if (node->children != NULL) { - const size_t num_children = 1 + (node->children->next != NULL); - Array children_array = { - .items = xmalloc(num_children * sizeof(children_array.items[0])), - .capacity = num_children, - .size = num_children, - }; - for (size_t i = 0; i < num_children; i++) { - children_array.items[i] = NIL; - } - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("children"), - .value = ARRAY_OBJ(children_array), - }; - kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { - .node_p = &node->children, - .ret_node_p = &children_array.items[0], - })); - } else if (node->next != NULL) { - kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { - .node_p = &node->next, - .ret_node_p = cur_item.ret_node_p + 1, - })); - } else { - kv_drop(ast_conv_stack, 1); - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("type"), - .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), - }; - Array start_array = { - .items = xmalloc(2 * sizeof(start_array.items[0])), - .capacity = 2, - .size = 2, - }; - start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); - start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("start"), - .value = ARRAY_OBJ(start_array), - }; - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("len"), - .value = INTEGER_OBJ((Integer)node->len), - }; - switch (node->type) { - case kExprNodeDoubleQuotedString: - case kExprNodeSingleQuotedString: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("svalue"), - .value = STRING_OBJ(((String) { - .data = node->data.str.value, - .size = node->data.str.size, - })), - }; - break; - case kExprNodeOption: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("scope"), - .value = INTEGER_OBJ(node->data.opt.scope), - }; - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("ident"), - .value = STRING_OBJ(((String) { - .data = xmemdupz(node->data.opt.ident, - node->data.opt.ident_len), - .size = node->data.opt.ident_len, - })), - }; - break; - case kExprNodePlainIdentifier: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("scope"), - .value = INTEGER_OBJ(node->data.var.scope), - }; - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("ident"), - .value = STRING_OBJ(((String) { - .data = xmemdupz(node->data.var.ident, - node->data.var.ident_len), - .size = node->data.var.ident_len, - })), - }; - break; - case kExprNodePlainKey: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("ident"), - .value = STRING_OBJ(((String) { - .data = xmemdupz(node->data.var.ident, - node->data.var.ident_len), - .size = node->data.var.ident_len, - })), - }; - break; - case kExprNodeEnvironment: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("ident"), - .value = STRING_OBJ(((String) { - .data = xmemdupz(node->data.env.ident, - node->data.env.ident_len), - .size = node->data.env.ident_len, - })), - }; - break; - case kExprNodeRegister: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("name"), - .value = INTEGER_OBJ(node->data.reg.name), - }; - break; - case kExprNodeComparison: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("cmp_type"), - .value = STRING_OBJ(cstr_to_string(eltkn_cmp_type_tab[node->data.cmp.type])), - }; - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("ccs_strategy"), - .value = STRING_OBJ(cstr_to_string(ccs_tab[node->data.cmp.ccs])), - }; - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("invert"), - .value = BOOLEAN_OBJ(node->data.cmp.inv), - }; - break; - case kExprNodeFloat: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("fvalue"), - .value = FLOAT_OBJ(node->data.flt.value), - }; - break; - case kExprNodeInteger: - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("ivalue"), - .value = INTEGER_OBJ((Integer)( - node->data.num.value > API_INTEGER_MAX - ? API_INTEGER_MAX - : (Integer)node->data.num.value)), - }; - break; - case kExprNodeAssignment: { - const ExprAssignmentType asgn_type = node->data.ass.type; - ret_node->items[ret_node->size++] = (KeyValuePair) { - .key = STATIC_CSTR_TO_STRING("augmentation"), - .value = STRING_OBJ(asgn_type == kExprAsgnPlain - ? (String)STRING_INIT - : cstr_to_string(expr_asgn_type_tab[asgn_type])), - }; - break; - } - case kExprNodeMissing: - case kExprNodeOpMissing: - case kExprNodeTernary: - case kExprNodeTernaryValue: - case kExprNodeSubscript: - case kExprNodeListLiteral: - case kExprNodeUnaryPlus: - case kExprNodeBinaryPlus: - case kExprNodeNested: - case kExprNodeCall: - case kExprNodeComplexIdentifier: - case kExprNodeUnknownFigure: - case kExprNodeLambda: - case kExprNodeDictLiteral: - case kExprNodeCurlyBracesIdentifier: - case kExprNodeComma: - case kExprNodeColon: - case kExprNodeArrow: - case kExprNodeConcat: - case kExprNodeConcatOrSubscript: - case kExprNodeOr: - case kExprNodeAnd: - case kExprNodeUnaryMinus: - case kExprNodeBinaryMinus: - case kExprNodeNot: - case kExprNodeMultiplication: - case kExprNodeDivision: - case kExprNodeMod: - break; - } - assert(cur_item.ret_node_p->data.dictionary.size - == cur_item.ret_node_p->data.dictionary.capacity); - xfree(*cur_item.node_p); - *cur_item.node_p = NULL; - } - } - } - kvi_destroy(ast_conv_stack); - - assert(ret.size == ret.capacity); - // Should be a no-op actually, leaving it in case non-nodes will need to be - // freed later. - viml_pexpr_free_ast(east); - viml_parser_destroy(&pstate); - return ret; -} - - /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing /// later. diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c new file mode 100644 index 0000000000..ac68b0eb91 --- /dev/null +++ b/src/nvim/api/vimscript.c @@ -0,0 +1,733 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include +#include +#include + +#include "nvim/ascii.h" +#include "nvim/api/vimscript.h" +#include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds2.h" +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/vimscript.c.generated.h" +#endif + +/// Executes Vimscript (multiline block of Ex-commands), like anonymous +/// |:source|. +/// +/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:), +/// etc. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @see |execute()| +/// @see |nvim_command()| +/// +/// @param src Vimscript code +/// @param output Capture and return all (non-error, non-shell |:!|) output +/// @param[out] err Error details (Vim error), if any +/// @return Output (non-error, non-shell |:!|) if `output` is true, +/// else empty string. +String nvim_exec(String src, Boolean output, Error *err) + FUNC_API_SINCE(7) +{ + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + if (output) { + ga_init(&capture_local, 1, 80); + capture_ga = &capture_local; + } + + try_start(); + if (output) { + msg_silent++; + } + do_source_str(src.data, "nvim_exec()"); + if (output) { + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + } + try_end(err); + + if (ERROR_SET(err)) { + goto theend; + } + + if (output && capture_local.ga_len > 1) { + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, + }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size - 1); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. + } +theend: + if (output) { + ga_clear(&capture_local); + } + return (String)STRING_INIT; +} + +/// Executes an ex-command. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @see |nvim_exec()| +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +void nvim_command(String command, Error *err) + FUNC_API_SINCE(1) +{ + try_start(); + do_cmdline_cmd(command.data); + try_end(err); +} + +/// Evaluates a VimL |expression|. +/// Dictionaries and Lists are recursively expanded. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param expr VimL expression string +/// @param[out] err Error details, if any +/// @return Evaluation result or expanded object +Object nvim_eval(String expr, Error *err) + FUNC_API_SINCE(1) +{ + static int recursive = 0; // recursion depth + Object rv = OBJECT_INIT; + + TRY_WRAP({ + // Initialize `force_abort` and `suppress_errthrow` at the top level. + if (!recursive) { + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + // `did_emsg` is set by emsg(), which cancels execution. + did_emsg = false; + } + recursive++; + try_start(); + + typval_T rettv; + int ok = eval0((char_u *)expr.data, &rettv, NULL, true); + + if (!try_end(err)) { + if (ok == FAIL) { + // Should never happen, try_end() should get the error. #8371 + api_set_error(err, kErrorTypeException, + "Failed to evaluate expression: '%.*s'", 256, expr.data); + } else { + rv = vim_to_object(&rettv); + } + } + + tv_clear(&rettv); + recursive--; + }); + + return rv; +} + +/// Calls a VimL function. +/// +/// @param fn Function name +/// @param args Function arguments +/// @param self `self` dict, or NULL for non-dict functions +/// @param[out] err Error details, if any +/// @return Result of the function call +static Object _call_function(String fn, Array args, dict_T *self, Error *err) +{ + static int recursive = 0; // recursion depth + Object rv = OBJECT_INIT; + + if (args.size > MAX_FUNC_ARGS) { + api_set_error(err, kErrorTypeValidation, + "Function called with too many arguments"); + return rv; + } + + // Convert the arguments in args from Object to typval_T values + typval_T vim_args[MAX_FUNC_ARGS + 1]; + size_t i = 0; // also used for freeing the variables + for (; i < args.size; i++) { + if (!object_to_vim(args.items[i], &vim_args[i], err)) { + goto free_vim_args; + } + } + + TRY_WRAP({ + // Initialize `force_abort` and `suppress_errthrow` at the top level. + if (!recursive) { + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + // `did_emsg` is set by emsg(), which cancels execution. + did_emsg = false; + } + recursive++; + try_start(); + typval_T rettv; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.selfdict = self; + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (see above) to capture abort-causing non-exception errors. + (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, + vim_args, &funcexe); + if (!try_end(err)) { + rv = vim_to_object(&rettv); + } + tv_clear(&rettv); + recursive--; + }); + +free_vim_args: + while (i > 0) { + tv_clear(&vim_args[--i]); + } + + return rv; +} + +/// Calls a VimL function with the given arguments. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param fn Function to call +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_function(String fn, Array args, Error *err) + FUNC_API_SINCE(1) +{ + return _call_function(fn, args, NULL, err); +} + +/// Calls a VimL |Dictionary-function| with the given arguments. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param dict Dictionary, or String evaluating to a VimL |self| dict +/// @param fn Name of the function defined on the VimL dict +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) + FUNC_API_SINCE(4) +{ + Object rv = OBJECT_INIT; + + typval_T rettv; + bool mustfree = false; + switch (dict.type) { + case kObjectTypeString: + try_start(); + if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) { + api_set_error(err, kErrorTypeException, + "Failed to evaluate dict expression"); + } + if (try_end(err)) { + return rv; + } + // Evaluation of the string arg created a new dict or increased the + // refcount of a dict. Not necessary for a RPC dict. + mustfree = true; + break; + case kObjectTypeDictionary: + if (!object_to_vim(dict, &rettv, err)) { + goto end; + } + break; + default: + api_set_error(err, kErrorTypeValidation, + "dict argument type must be String or Dictionary"); + return rv; + } + dict_T *self_dict = rettv.vval.v_dict; + if (rettv.v_type != VAR_DICT || !self_dict) { + api_set_error(err, kErrorTypeValidation, "dict not found"); + goto end; + } + + if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) { + dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data); + goto end; + } + if (di->di_tv.v_type == VAR_PARTIAL) { + api_set_error(err, kErrorTypeValidation, + "partial function not supported"); + goto end; + } + if (di->di_tv.v_type != VAR_FUNC) { + api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data); + goto end; + } + fn = (String) { + .data = (char *)di->di_tv.vval.v_string, + .size = STRLEN(di->di_tv.vval.v_string), + }; + } + + if (!fn.data || fn.size < 1) { + api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name"); + goto end; + } + + rv = _call_function(fn, args, self_dict, err); +end: + if (mustfree) { + tv_clear(&rettv); + } + + return rv; +} + +typedef struct { + ExprASTNode **node_p; + Object *ret_node_p; +} ExprASTConvStackItem; + +/// @cond DOXYGEN_NOT_A_FUNCTION +typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; +/// @endcond + +/// Parse a VimL expression. +/// +/// @param[in] expr Expression to parse. Always treated as a single line. +/// @param[in] flags Flags: +/// - "m" if multiple expressions in a row are allowed (only +/// the first one will be parsed), +/// - "E" if EOC tokens are not allowed (determines whether +/// they will stop parsing process or be recognized as an +/// operator/space, though also yielding an error). +/// - "l" when needing to start parsing with lvalues for +/// ":let" or ":for". +/// Common flag sets: +/// - "m" to parse like for ":echo". +/// - "E" to parse like for "=". +/// - empty string for ":call". +/// - "lm" to parse for ":let". +/// @param[in] highlight If true, return value will also include "highlight" +/// key containing array of 4-tuples (arrays) (Integer, +/// Integer, Integer, String), where first three numbers +/// define the highlighted region and represent line, +/// starting column and ending column (latter exclusive: +/// one should highlight region [start_col, end_col)). +/// +/// @return +/// - AST: top-level dictionary with these keys: +/// - "error": Dictionary with error, present only if parser saw some +/// error. Contains the following keys: +/// - "message": String, error message in printf format, translated. +/// Must contain exactly one "%.*s". +/// - "arg": String, error message argument. +/// - "len": Amount of bytes successfully parsed. With flags equal to "" +/// that should be equal to the length of expr string. +/// (“Successfully parsed” here means “participated in AST +/// creation”, not “till the first error”.) +/// - "ast": AST, either nil or a dictionary with these keys: +/// - "type": node type, one of the value names from ExprASTNodeType +/// stringified without "kExprNode" prefix. +/// - "start": a pair [line, column] describing where node is "started" +/// where "line" is always 0 (will not be 0 if you will be +/// using nvim_parse_viml() on e.g. ":let", but that is not +/// present yet). Both elements are Integers. +/// - "len": “length” of the node. This and "start" are there for +/// debugging purposes primary (debugging parser and providing +/// debug information). +/// - "children": a list of nodes described in top/"ast". There always +/// is zero, one or two children, key will not be present +/// if node has no children. Maximum number of children +/// may be found in node_maxchildren array. +/// - Local values (present only for certain nodes): +/// - "scope": a single Integer, specifies scope for "Option" and +/// "PlainIdentifier" nodes. For "Option" it is one of +/// ExprOptScope values, for "PlainIdentifier" it is one of +/// ExprVarScope values. +/// - "ident": identifier (without scope, if any), present for "Option", +/// "PlainIdentifier", "PlainKey" and "Environment" nodes. +/// - "name": Integer, register name (one character) or -1. Only present +/// for "Register" nodes. +/// - "cmp_type": String, comparison type, one of the value names from +/// ExprComparisonType, stringified without "kExprCmp" +/// prefix. Only present for "Comparison" nodes. +/// - "ccs_strategy": String, case comparison strategy, one of the +/// value names from ExprCaseCompareStrategy, +/// stringified without "kCCStrategy" prefix. Only +/// present for "Comparison" nodes. +/// - "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. +/// - "invert": Boolean, true if result of comparison needs to be +/// inverted. Only present for "Comparison" nodes. +/// - "ivalue": Integer, integer value for "Integer" nodes. +/// - "fvalue": Float, floating-point value for "Float" nodes. +/// - "svalue": String, value for "SingleQuotedString" and +/// "DoubleQuotedString" nodes. +/// @param[out] err Error details, if any +Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err) + FUNC_API_SINCE(4) FUNC_API_FAST +{ + int pflags = 0; + for (size_t i = 0 ; i < flags.size ; i++) { + switch (flags.data[i]) { + case 'm': + pflags |= kExprFlagsMulti; break; + case 'E': + pflags |= kExprFlagsDisallowEOC; break; + case 'l': + pflags |= kExprFlagsParseLet; break; + case NUL: + api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", + (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + default: + api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", + flags.data[i], (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + } + ParserLine parser_lines[] = { + { + .data = expr.data, + .size = expr.size, + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = parser_lines; + ParserHighlight colors; + kvi_init(colors); + ParserHighlight *const colors_p = (highlight ? &colors : NULL); + ParserState pstate; + viml_parser_init(&pstate, parser_simple_get_line, &plines_p, colors_p); + ExprAST east = viml_pexpr_parse(&pstate, pflags); + + const size_t ret_size = (2 // "ast", "len" + + (size_t)(east.err.msg != NULL) // "error" + + (size_t)highlight // "highlight" + + 0); + Dictionary ret = { + .items = xmalloc(ret_size * sizeof(ret.items[0])), + .size = 0, + .capacity = ret_size, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ast"), + .value = NIL, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 + ? parser_lines[0].size + : pstate.pos.col)), + }; + if (east.err.msg != NULL) { + Dictionary err_dict = { + .items = xmalloc(2 * sizeof(err_dict.items[0])), + .size = 2, + .capacity = 2, + }; + err_dict.items[0] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("message"), + .value = STRING_OBJ(cstr_to_string(east.err.msg)), + }; + if (east.err.arg == NULL) { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(STRING_INIT), + }; + } else { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("error"), + .value = DICTIONARY_OBJ(err_dict), + }; + } + if (highlight) { + Array hl = (Array) { + .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), + .capacity = kv_size(colors), + .size = kv_size(colors), + }; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + Array chunk_arr = (Array) { + .items = xmalloc(4 * sizeof(chunk_arr.items[0])), + .capacity = 4, + .size = 4, + }; + chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); + chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); + chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); + chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); + hl.items[i] = ARRAY_OBJ(chunk_arr); + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("highlight"), + .value = ARRAY_OBJ(hl), + }; + } + kvi_destroy(colors); + + // Walk over the AST, freeing nodes in process. + ExprASTConvStack ast_conv_stack; + kvi_init(ast_conv_stack); + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &east.root, + .ret_node_p = &ret.items[0].value, + })); + while (kv_size(ast_conv_stack)) { + ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); + ExprASTNode *const node = *cur_item.node_p; + if (node == NULL) { + assert(kv_size(ast_conv_stack) == 1); + kv_drop(ast_conv_stack, 1); + } else { + if (cur_item.ret_node_p->type == kObjectTypeNil) { + size_t items_size = (size_t)(3 // "type", "start" and "len" + + (node->children != NULL) // "children" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier) // "scope" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier + || node->type == kExprNodePlainKey + || node->type == kExprNodeEnvironment) // "ident" + + (node->type == kExprNodeRegister) // "name" + + (3 // "cmp_type", "ccs_strategy", "invert" + * (node->type == kExprNodeComparison)) + + (node->type == kExprNodeInteger) // "ivalue" + + (node->type == kExprNodeFloat) // "fvalue" + + (node->type == kExprNodeDoubleQuotedString + || node->type == kExprNodeSingleQuotedString) // "svalue" + + (node->type == kExprNodeAssignment) // "augmentation" + + 0); + Dictionary ret_node = { + .items = xmalloc(items_size * sizeof(ret_node.items[0])), + .capacity = items_size, + .size = 0, + }; + *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); + } + Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; + if (node->children != NULL) { + const size_t num_children = 1 + (node->children->next != NULL); + Array children_array = { + .items = xmalloc(num_children * sizeof(children_array.items[0])), + .capacity = num_children, + .size = num_children, + }; + for (size_t i = 0; i < num_children; i++) { + children_array.items[i] = NIL; + } + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("children"), + .value = ARRAY_OBJ(children_array), + }; + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = &children_array.items[0], + })); + } else if (node->next != NULL) { + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->next, + .ret_node_p = cur_item.ret_node_p + 1, + })); + } else { + kv_drop(ast_conv_stack, 1); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("type"), + .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), + }; + Array start_array = { + .items = xmalloc(2 * sizeof(start_array.items[0])), + .capacity = 2, + .size = 2, + }; + start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); + start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("start"), + .value = ARRAY_OBJ(start_array), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)node->len), + }; + switch (node->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("svalue"), + .value = STRING_OBJ(((String) { + .data = node->data.str.value, + .size = node->data.str.size, + })), + }; + break; + case kExprNodeOption: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.opt.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.opt.ident, + node->data.opt.ident_len), + .size = node->data.opt.ident_len, + })), + }; + break; + case kExprNodePlainIdentifier: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.var.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + case kExprNodePlainKey: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + case kExprNodeEnvironment: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.env.ident, + node->data.env.ident_len), + .size = node->data.env.ident_len, + })), + }; + break; + case kExprNodeRegister: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("name"), + .value = INTEGER_OBJ(node->data.reg.name), + }; + break; + case kExprNodeComparison: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("cmp_type"), + .value = STRING_OBJ(cstr_to_string(eltkn_cmp_type_tab[node->data.cmp.type])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ccs_strategy"), + .value = STRING_OBJ(cstr_to_string(ccs_tab[node->data.cmp.ccs])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("invert"), + .value = BOOLEAN_OBJ(node->data.cmp.inv), + }; + break; + case kExprNodeFloat: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("fvalue"), + .value = FLOAT_OBJ(node->data.flt.value), + }; + break; + case kExprNodeInteger: + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ivalue"), + .value = INTEGER_OBJ((Integer)(node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), + }; + break; + case kExprNodeAssignment: { + const ExprAssignmentType asgn_type = node->data.ass.type; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("augmentation"), + .value = STRING_OBJ(asgn_type == kExprAsgnPlain + ? (String)STRING_INIT + : cstr_to_string(expr_asgn_type_tab[asgn_type])), + }; + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: + break; + } + assert(cur_item.ret_node_p->data.dictionary.size + == cur_item.ret_node_p->data.dictionary.capacity); + xfree(*cur_item.node_p); + *cur_item.node_p = NULL; + } + } + } + kvi_destroy(ast_conv_stack); + + assert(ret.size == ret.capacity); + // Should be a no-op actually, leaving it in case non-nodes will need to be + // freed later. + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + return ret; +} diff --git a/src/nvim/api/vimscript.h b/src/nvim/api/vimscript.h new file mode 100644 index 0000000000..be808b6b25 --- /dev/null +++ b/src/nvim/api/vimscript.h @@ -0,0 +1,9 @@ +#ifndef NVIM_API_VIMSCRIPT_H +#define NVIM_API_VIMSCRIPT_H + +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/vimscript.h.generated.h" +#endif +#endif // NVIM_API_VIMSCRIPT_H diff --git a/src/nvim/context.c b/src/nvim/context.c index 911139dfdf..4a8e645d2c 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -6,6 +6,7 @@ #include "nvim/api/private/converter.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" +#include "nvim/api/vimscript.h" #include "nvim/context.h" #include "nvim/eval/encode.h" #include "nvim/ex_docmd.h" diff --git a/test/unit/api/private_helpers_spec.lua b/test/unit/api/private_helpers_spec.lua index a534d83165..dbb4b3ae5a 100644 --- a/test/unit/api/private_helpers_spec.lua +++ b/test/unit/api/private_helpers_spec.lua @@ -18,7 +18,7 @@ local type_key = api_helpers.type_key local obj2lua = api_helpers.obj2lua local func_type = api_helpers.func_type -local api = cimport('./src/nvim/api/private/helpers.h') +local api = cimport('./src/nvim/api/private/helpers.h', './src/nvim/api/private/converter.h') describe('vim_to_object', function() local vim_to_object = function(l)