mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	refactor(api): break out Vim script functions to its own file
This commit is contained in:
		| @@ -89,7 +89,9 @@ CONFIG = { | ||||
|         # Section ordering. | ||||
|         'section_order': [ | ||||
|             'vim.c', | ||||
|             'vimscript.c', | ||||
|             'buffer.c', | ||||
|             'extmark.c', | ||||
|             'window.c', | ||||
|             'win_config.c', | ||||
|             'tabpage.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" | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
							
								
								
									
										733
									
								
								src/nvim/api/vimscript.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										733
									
								
								src/nvim/api/vimscript.c
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/nvim/api/vimscript.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/nvim/api/vimscript.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Björn Linse
					Björn Linse