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 ordering. | ||||||
|         'section_order': [ |         'section_order': [ | ||||||
|             'vim.c', |             'vim.c', | ||||||
|  |             'vimscript.c', | ||||||
|             'buffer.c', |             'buffer.c', | ||||||
|  |             'extmark.c', | ||||||
|             'window.c', |             'window.c', | ||||||
|             'win_config.c', |             'win_config.c', | ||||||
|             'tabpage.c', |             'tabpage.c', | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ | |||||||
| #include "nvim/api/private/defs.h" | #include "nvim/api/private/defs.h" | ||||||
| #include "nvim/api/private/helpers.h" | #include "nvim/api/private/helpers.h" | ||||||
| #include "nvim/api/vim.h" | #include "nvim/api/vim.h" | ||||||
|  | #include "nvim/api/vimscript.h" | ||||||
| #include "nvim/extmark.h" | #include "nvim/extmark.h" | ||||||
| #include "nvim/lua/executor.h" | #include "nvim/lua/executor.h" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ | |||||||
| #include "nvim/api/tabpage.h" | #include "nvim/api/tabpage.h" | ||||||
| #include "nvim/api/ui.h" | #include "nvim/api/ui.h" | ||||||
| #include "nvim/api/vim.h" | #include "nvim/api/vim.h" | ||||||
|  | #include "nvim/api/vimscript.h" | ||||||
| #include "nvim/api/win_config.h" | #include "nvim/api/win_config.h" | ||||||
| #include "nvim/api/window.h" | #include "nvim/api/window.h" | ||||||
| #include "nvim/log.h" | #include "nvim/log.h" | ||||||
|   | |||||||
| @@ -60,84 +60,6 @@ | |||||||
| # include "api/vim.c.generated.h" | # include "api/vim.c.generated.h" | ||||||
| #endif | #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. | /// Gets a highlight definition by name. | ||||||
| /// | /// | ||||||
| /// @param name Highlight group 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); |   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 | /// Execute Lua code. Parameters (if any) are available as `...` inside the | ||||||
| /// chunk. The chunk can return a value. | /// 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); |   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`. | /// Calculates the number of display cells occupied by `text`. | ||||||
| /// <Tab> counts as one cell. | /// <Tab> counts as one cell. | ||||||
| /// | /// | ||||||
| @@ -1991,439 +1710,6 @@ theend: | |||||||
|   return rv; |   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 | /// Writes a message to vim output or error buffer. The string is split | ||||||
| /// and flushed after each newline. Incomplete lines are kept for writing | /// and flushed after each newline. Incomplete lines are kept for writing | ||||||
| /// later. | /// 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/converter.h" | ||||||
| #include "nvim/api/private/helpers.h" | #include "nvim/api/private/helpers.h" | ||||||
| #include "nvim/api/vim.h" | #include "nvim/api/vim.h" | ||||||
|  | #include "nvim/api/vimscript.h" | ||||||
| #include "nvim/context.h" | #include "nvim/context.h" | ||||||
| #include "nvim/eval/encode.h" | #include "nvim/eval/encode.h" | ||||||
| #include "nvim/ex_docmd.h" | #include "nvim/ex_docmd.h" | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ local type_key = api_helpers.type_key | |||||||
| local obj2lua = api_helpers.obj2lua | local obj2lua = api_helpers.obj2lua | ||||||
| local func_type = api_helpers.func_type | 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() | describe('vim_to_object', function() | ||||||
|   local vim_to_object = function(l) |   local vim_to_object = function(l) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Björn Linse
					Björn Linse