aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/gen_vimdoc.py2
-rw-r--r--src/nvim/api/deprecated.c1
-rw-r--r--src/nvim/api/private/dispatch.c1
-rw-r--r--src/nvim/api/vim.c714
-rw-r--r--src/nvim/api/vimscript.c733
-rw-r--r--src/nvim/api/vimscript.h9
-rw-r--r--src/nvim/context.c1
-rw-r--r--test/unit/api/private_helpers_spec.lua2
8 files changed, 748 insertions, 715 deletions
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`.
/// <Tab> 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 "<C-r>=".
-/// - 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 <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#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 "<C-r>=".
+/// - 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)