mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 13:15:09 -07:00
refactor(api): break out Vim script functions to its own file
This commit is contained in:
parent
c3a3e65428
commit
a60beeb34f
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user