aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2017-05-09 00:39:17 +0200
committerGitHub <noreply@github.com>2017-05-09 00:39:17 +0200
commit0e873a30f3072dbacfb700f1e331a8c8396f2e1f (patch)
tree557792d454fef510a90975bed4e7d1650ee26c4f /src
parenta9981e0e7e9439340bb8c0162f860b78d8002559 (diff)
parent5b6d598ca8301682d931539ecd6da6a9fabae569 (diff)
downloadrneovim-0e873a30f3072dbacfb700f1e331a8c8396f2e1f.tar.gz
rneovim-0e873a30f3072dbacfb700f1e331a8c8396f2e1f.tar.bz2
rneovim-0e873a30f3072dbacfb700f1e331a8c8396f2e1f.zip
Merge #4411 from ZyX-I/luaviml'/lua
Diffstat (limited to 'src')
-rwxr-xr-xsrc/clint.py5
-rw-r--r--src/nvim/CMakeLists.txt129
-rw-r--r--src/nvim/api/buffer.c45
-rw-r--r--src/nvim/api/private/defs.h34
-rw-r--r--src/nvim/api/private/helpers.c11
-rw-r--r--src/nvim/api/private/helpers.h2
-rw-r--r--src/nvim/api/vim.c59
-rw-r--r--src/nvim/api/window.c8
-rw-r--r--src/nvim/eval.c19
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/decode.c149
-rw-r--r--src/nvim/eval/decode.h1
-rw-r--r--src/nvim/eval/typval_encode.c.h5
-rw-r--r--src/nvim/ex_cmds.lua6
-rw-r--r--src/nvim/ex_cmds2.c11
-rw-r--r--src/nvim/ex_docmd.c13
-rw-r--r--src/nvim/ex_getln.c68
-rw-r--r--src/nvim/func_attr.h5
-rw-r--r--src/nvim/lua/converter.c1186
-rw-r--r--src/nvim/lua/converter.h15
-rw-r--r--src/nvim/lua/executor.c576
-rw-r--r--src/nvim/lua/executor.h25
-rw-r--r--src/nvim/lua/vim.lua2
-rw-r--r--src/nvim/msgpack_rpc/helpers.c29
-rw-r--r--src/nvim/msgpack_rpc/helpers.h7
25 files changed, 2227 insertions, 184 deletions
diff --git a/src/clint.py b/src/clint.py
index 5174521fb8..3babb7772b 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -182,6 +182,7 @@ _ERROR_CATEGORIES = [
'build/include_order',
'build/printf_format',
'build/storage_class',
+ 'build/useless_fattr',
'readability/alt_tokens',
'readability/bool',
'readability/braces',
@@ -1225,6 +1226,10 @@ def CheckForHeaderGuard(filename, lines, error):
lines: An array of strings, each representing a line of the file.
error: The function to call with any errors found.
"""
+ if filename.endswith('.c.h') or FileInfo(filename).RelativePath() in set((
+ 'func_attr.h',
+ )):
+ return
cppvar = GetHeaderGuardCPPVariable(filename)
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index f69781a539..8deeff4654 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -12,11 +12,10 @@ endif()
set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches)
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
-set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendispatch.lua)
-file(GLOB API_HEADERS api/*.h)
-file(GLOB MSGPACK_RPC_HEADERS msgpack_rpc/*.h)
+set(MSGPACK_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genmsgpack.lua)
set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack)
set(FUNCS_DATA ${PROJECT_BINARY_DIR}/funcs_data.mpack)
+set(MSGPACK_LUA_C_BINDINGS ${GENERATED_DIR}/msgpack_lua_c_bindings.generated.c)
set(HEADER_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendeclarations.lua)
set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include)
set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch_wrappers.generated.h)
@@ -33,8 +32,10 @@ set(EVENTS_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gen_events.lua)
set(OPTIONS_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genoptions.lua)
set(UNICODE_TABLES_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genunicodetables.lua)
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode)
-file(GLOB UNICODE_FILES ${UNICODE_DIR}/*.txt)
set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
+set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
+set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(CHAR_BLOB_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gencharblob.lua)
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
set(LINT_SUPPRESS_URL "${LINT_SUPPRESS_URL_BASE}/errors.json")
@@ -46,6 +47,10 @@ set(LINT_SUPPRESSES_ARCHIVE ${LINT_SUPPRESSES_ROOT}/errors.tar.gz)
set(LINT_SUPPRESSES_TOUCH_FILE "${TOUCHES_DIR}/unpacked-clint-errors-archive")
set(LINT_SUPPRESSES_INSTALL_SCRIPT "${PROJECT_SOURCE_DIR}/cmake/InstallClintErrors.cmake")
+file(GLOB UNICODE_FILES ${UNICODE_DIR}/*.txt)
+file(GLOB API_HEADERS api/*.h)
+file(GLOB MSGPACK_RPC_HEADERS msgpack_rpc/*.h)
+
include_directories(${GENERATED_DIR})
include_directories(${CACHED_GENERATED_DIR})
include_directories(${GENERATED_INCLUDES_DIR})
@@ -67,6 +72,7 @@ foreach(subdir
tui
event
eval
+ lua
)
if(${subdir} MATCHES "tui" AND NOT FEAT_TUI)
continue()
@@ -157,7 +163,7 @@ if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN)
endif()
get_directory_property(gen_includes INCLUDE_DIRECTORIES)
-foreach(gen_include ${gen_includes})
+foreach(gen_include ${gen_includes} ${LUAJIT_INCLUDE_DIRS})
list(APPEND gen_cflags "-I${gen_include}")
endforeach()
string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
@@ -219,18 +225,34 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES}
${UNICODE_FILES}
)
-add_custom_command(OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA}
- ${API_METADATA}
- COMMAND ${LUA_PRG} ${DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
- ${API_HEADERS} ${GENERATED_API_DISPATCH}
+add_custom_command(
+ OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA}
+ ${API_METADATA} ${MSGPACK_LUA_C_BINDINGS}
+ COMMAND ${LUA_PRG} ${MSGPACK_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
+ ${GENERATED_API_DISPATCH}
${GENERATED_FUNCS_METADATA} ${API_METADATA}
+ ${MSGPACK_LUA_C_BINDINGS}
+ ${API_HEADERS}
DEPENDS
${API_HEADERS}
${MSGPACK_RPC_HEADERS}
- ${DISPATCH_GENERATOR}
+ ${MSGPACK_GENERATOR}
${CMAKE_CURRENT_LIST_DIR}/api/dispatch_deprecated.lua
)
+add_custom_command(
+ OUTPUT ${VIM_MODULE_FILE}
+ COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE}
+ ${VIM_MODULE_FILE} vim_module
+ DEPENDS
+ ${CHAR_BLOB_GENERATOR}
+ ${VIM_MODULE_SOURCE}
+)
+
+list(APPEND NVIM_GENERATED_SOURCES
+ "${MSGPACK_LUA_C_BINDINGS}"
+)
+
list(APPEND NVIM_GENERATED_FOR_HEADERS
"${GENERATED_EX_CMDS_ENUM}"
"${GENERATED_EVENTS_ENUM}"
@@ -242,6 +264,7 @@ list(APPEND NVIM_GENERATED_FOR_SOURCES
"${GENERATED_EVENTS_NAMES_MAP}"
"${GENERATED_OPTIONS}"
"${GENERATED_UNICODE_TABLES}"
+ "${VIM_MODULE_FILE}"
)
list(APPEND NVIM_GENERATED_SOURCES
@@ -315,6 +338,23 @@ if(UNIX)
endif()
set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES})
+set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES})
+
+if(CMAKE_VERSION VERSION_LESS "2.8.8")
+ if(PREFER_LUAJIT)
+ include_directories(${LUAJIT_INCLUDE_DIRS})
+ else()
+ message(FATAL_ERROR
+ "Must support INCLUDE_DIRECTORIES target property to build")
+ endif()
+endif()
+
+if(PREFER_LUAJIT)
+ list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUAJIT_LIBRARIES})
+else()
+ list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUA_LIBRARIES})
+endif()
+list(APPEND NVIM_TEST_LINK_LIBRARIES ${LUAJIT_LIBRARIES})
# Don't use jemalloc in the unit test library.
if(JEMALLOC_FOUND)
@@ -326,6 +366,14 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES})
install_helper(TARGETS nvim)
+if(PREFER_LUAJIT)
+ set(LUA_PREFERRED_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIRS})
+else()
+ set(LUA_PREFERRED_INCLUDE_DIRS ${LUA_INCLUDE_DIRS})
+endif()
+set_property(TARGET nvim APPEND PROPERTY
+ INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
+
if(WIN32)
# Copy DLLs and third-party tools to bin/ and install them along with nvim
add_custom_target(nvim_runtime_deps ALL
@@ -372,6 +420,50 @@ if(WIN32)
add_dependencies(nvim_runtime_deps external_blobs)
endif()
+add_library(
+ libnvim
+ STATIC
+ EXCLUDE_FROM_ALL
+ ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
+ ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
+)
+set_property(TARGET libnvim APPEND PROPERTY
+ INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS})
+target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES})
+set_target_properties(
+ libnvim
+ PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ OUTPUT_NAME nvim
+)
+set_property(
+ TARGET libnvim
+ APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB "
+)
+
+add_library(
+ nvim-test
+ MODULE
+ EXCLUDE_FROM_ALL
+ ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
+ ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
+ ${UNIT_TEST_FIXTURES}
+)
+target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES})
+set_property(
+ TARGET nvim-test
+ APPEND PROPERTY INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS}
+)
+set_target_properties(
+ nvim-test
+ PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+)
+set_property(
+ TARGET nvim-test
+ APPEND_STRING PROPERTY COMPILE_FLAGS " -DUNIT_TESTING "
+)
+
if(CLANG_ASAN_UBSAN)
message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.")
check_c_compiler_flag(-fno-sanitize-recover=all SANITIZE_RECOVER_ALL)
@@ -396,19 +488,6 @@ elseif(CLANG_TSAN)
set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=thread ")
endif()
-add_library(libnvim STATIC EXCLUDE_FROM_ALL ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS})
-target_link_libraries(libnvim ${NVIM_LINK_LIBRARIES})
-set_target_properties(libnvim PROPERTIES
- POSITION_INDEPENDENT_CODE ON
- OUTPUT_NAME nvim)
-set_property(TARGET libnvim APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB ")
-
-add_library(nvim-test MODULE EXCLUDE_FROM_ALL ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${UNIT_TEST_FIXTURES} ${NVIM_HEADERS})
-target_link_libraries(nvim-test ${NVIM_LINK_LIBRARIES})
-set_property(TARGET nvim-test APPEND_STRING PROPERTY COMPILE_FLAGS -DUNIT_TESTING)
-
function(get_test_target prefix sfile relative_path_var target_var)
get_filename_component(full_d "${sfile}" PATH)
file(RELATIVE_PATH d "${PROJECT_SOURCE_DIR}/src/nvim" "${full_d}")
@@ -446,6 +525,10 @@ foreach(hfile ${NVIM_HEADERS})
${texe}
EXCLUDE_FROM_ALL
${tsource} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_HEADERS})
+ set_property(
+ TARGET ${texe}
+ APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}
+ )
list(FIND NO_SINGLE_CHECK_HEADERS "${relative_path}" hfile_exclude_idx)
if(${hfile_exclude_idx} EQUAL -1)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 5de1535bce..0b8d39d0d2 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -195,7 +195,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
Object str = STRING_OBJ(cstr_to_string(bufstr));
// Vim represents NULs as NLs, but this may confuse clients.
- if (channel_id != INTERNAL_CALL) {
+ if (channel_id != VIML_INTERNAL_CALL) {
strchrsub(str.data.string.data, '\n', '\0');
}
@@ -295,6 +295,24 @@ void nvim_buf_set_lines(uint64_t channel_id,
return;
}
+ for (size_t i = 0; i < replacement.size; i++) {
+ if (replacement.items[i].type != kObjectTypeString) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "All items in the replacement array must be strings");
+ return;
+ }
+ // Disallow newlines in the middle of the line.
+ if (channel_id != VIML_INTERNAL_CALL) {
+ const String l = replacement.items[i].data.string;
+ if (memchr(l.data, NL, l.size)) {
+ api_set_error(err, kErrorTypeValidation,
+ "String cannot contain newlines");
+ return;
+ }
+ }
+ }
+
win_T *save_curwin = NULL;
tabpage_T *save_curtab = NULL;
size_t new_len = replacement.size;
@@ -303,27 +321,12 @@ void nvim_buf_set_lines(uint64_t channel_id,
char **lines = (new_len != 0) ? xcalloc(new_len, sizeof(char *)) : NULL;
for (size_t i = 0; i < new_len; i++) {
- if (replacement.items[i].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "All items in the replacement array must be strings");
- goto end;
- }
-
- String l = replacement.items[i].data.string;
+ const String l = replacement.items[i].data.string;
- // Fill lines[i] with l's contents. Disallow newlines in the middle of a
- // line and convert NULs to newlines to avoid truncation.
- lines[i] = xmallocz(l.size);
- for (size_t j = 0; j < l.size; j++) {
- if (l.data[j] == '\n' && channel_id != INTERNAL_CALL) {
- api_set_error(err, kErrorTypeException,
- "String cannot contain newlines");
- new_len = i + 1;
- goto end;
- }
- lines[i][j] = (char) (l.data[j] == '\0' ? '\n' : l.data[j]);
- }
+ // Fill lines[i] with l's contents. Convert NULs to newlines as required by
+ // NL-used-for-NUL.
+ lines[i] = xmemdupz(l.data, l.size);
+ memchrsub(lines[i], NUL, NL, l.size);
}
try_start();
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index 60bf38265f..2144c80d6a 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -5,6 +5,8 @@
#include <stdbool.h>
#include <string.h>
+#include "nvim/func_attr.h"
+
#define ARRAY_DICT_INIT {.size = 0, .capacity = 0, .items = NULL}
#define STRING_INIT {.data = NULL, .size = 0}
#define OBJECT_INIT { .type = kObjectTypeNil }
@@ -36,8 +38,27 @@ typedef enum {
/// Used as the message ID of notifications.
#define NO_RESPONSE UINT64_MAX
-/// Used as channel_id when the call is local.
-#define INTERNAL_CALL UINT64_MAX
+/// Mask for all internal calls
+#define INTERNAL_CALL_MASK (((uint64_t)1) << (sizeof(uint64_t) * 8 - 1))
+
+/// Internal call from VimL code
+#define VIML_INTERNAL_CALL INTERNAL_CALL_MASK
+
+/// Internal call from lua code
+#define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1)
+
+static inline bool is_internal_call(uint64_t channel_id)
+ REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST;
+
+/// Check whether call is internal
+///
+/// @param[in] channel_id Channel id.
+///
+/// @return true if channel_id refers to internal channel.
+static inline bool is_internal_call(const uint64_t channel_id)
+{
+ return !!(channel_id & INTERNAL_CALL_MASK);
+}
typedef struct {
ErrorType type;
@@ -78,16 +99,17 @@ typedef struct {
} Dictionary;
typedef enum {
- kObjectTypeBuffer,
- kObjectTypeWindow,
- kObjectTypeTabpage,
- kObjectTypeNil,
+ kObjectTypeNil = 0,
kObjectTypeBoolean,
kObjectTypeInteger,
kObjectTypeFloat,
kObjectTypeString,
kObjectTypeArray,
kObjectTypeDictionary,
+ // EXT types, cannot be split or reordered, see #EXT_OBJECT_TYPE_SHIFT
+ kObjectTypeBuffer,
+ kObjectTypeWindow,
+ kObjectTypeTabpage,
} ObjectType;
struct object {
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 69cb19c14f..4cc4b600d5 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -353,7 +353,7 @@ void set_option_to(void *to, int type, String name, Object value, Error *err)
#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER
#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
- kv_push(edata->stack, FLOATING_OBJ((Float)(flt)))
+ kv_push(edata->stack, FLOAT_OBJ((Float)(flt)))
#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
do { \
@@ -864,15 +864,18 @@ static void init_type_metadata(Dictionary *metadata)
Dictionary types = ARRAY_DICT_INIT;
Dictionary buffer_metadata = ARRAY_DICT_INIT;
- PUT(buffer_metadata, "id", INTEGER_OBJ(kObjectTypeBuffer));
+ PUT(buffer_metadata, "id",
+ INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT));
PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_")));
Dictionary window_metadata = ARRAY_DICT_INIT;
- PUT(window_metadata, "id", INTEGER_OBJ(kObjectTypeWindow));
+ PUT(window_metadata, "id",
+ INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT));
PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_")));
Dictionary tabpage_metadata = ARRAY_DICT_INIT;
- PUT(tabpage_metadata, "id", INTEGER_OBJ(kObjectTypeTabpage));
+ PUT(tabpage_metadata, "id",
+ INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT));
PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_")));
PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata));
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 681945ac9c..159b9d5c2a 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -18,7 +18,7 @@
.type = kObjectTypeInteger, \
.data.integer = i })
-#define FLOATING_OBJ(f) ((Object) { \
+#define FLOAT_OBJ(f) ((Object) { \
.type = kObjectTypeFloat, \
.data.floating = f })
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 11f15b5ad1..12e8194c15 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -144,7 +144,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
{
if (str.size == 0) {
// Empty string
- return str;
+ return (String) { .data = NULL, .size = 0 };
}
char *ptr = NULL;
@@ -203,7 +203,8 @@ Object nvim_eval(String expr, Error *err)
return rv;
}
-/// Calls a VimL function with the given arguments.
+/// Calls a VimL function with the given arguments
+///
/// On VimL error: Returns a generic error; v:errmsg is not updated.
///
/// @param fname Function to call
@@ -855,3 +856,57 @@ static void write_msg(String message, bool to_err)
--no_wait_return;
msg_end();
}
+
+// Functions used for testing purposes
+
+/// Returns object given as argument
+///
+/// This API function is used for testing. One should not rely on its presence
+/// in plugins.
+///
+/// @param[in] obj Object to return.
+///
+/// @return its argument.
+Object nvim__id(Object obj)
+{
+ return copy_object(obj);
+}
+
+/// Returns array given as argument
+///
+/// This API function is used for testing. One should not rely on its presence
+/// in plugins.
+///
+/// @param[in] arr Array to return.
+///
+/// @return its argument.
+Array nvim__id_array(Array arr)
+{
+ return copy_object(ARRAY_OBJ(arr)).data.array;
+}
+
+/// Returns dictionary given as argument
+///
+/// This API function is used for testing. One should not rely on its presence
+/// in plugins.
+///
+/// @param[in] dct Dictionary to return.
+///
+/// @return its argument.
+Dictionary nvim__id_dictionary(Dictionary dct)
+{
+ return copy_object(DICTIONARY_OBJ(dct)).data.dictionary;
+}
+
+/// Returns floating-point value given as argument
+///
+/// This API function is used for testing. One should not rely on its presence
+/// in plugins.
+///
+/// @param[in] flt Value to return.
+///
+/// @return its argument.
+Float nvim__id_float(Float flt)
+{
+ return flt;
+}
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 859bf88398..22902800ea 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -62,6 +62,10 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
{
win_T *win = find_window_by_handle(window, err);
+ if (!win) {
+ return;
+ }
+
if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger
|| pos.items[1].type != kObjectTypeInteger) {
api_set_error(err,
@@ -70,10 +74,6 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
return;
}
- if (!win) {
- return;
- }
-
int64_t row = pos.items[0].data.integer;
int64_t col = pos.items[1].data.integer;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 1bf85ccd8b..56a6632fad 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -97,6 +97,7 @@
#include "nvim/lib/kvec.h"
#include "nvim/lib/khash.h"
#include "nvim/lib/queue.h"
+#include "nvim/lua/executor.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/executor.h"
#include "nvim/eval/gc.h"
@@ -6510,7 +6511,7 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
Error err = ERROR_INIT;
- Object result = fn(INTERNAL_CALL, args, &err);
+ Object result = fn(VIML_INTERNAL_CALL, args, &err);
if (ERROR_SET(&err)) {
nvim_err_writeln(cstr_as_string(err.msg));
@@ -10973,7 +10974,9 @@ static int inputsecret_flag = 0;
* prompt. The third argument to f_inputdialog() specifies the value to return
* when the user cancels the prompt.
*/
-static void get_user_input(typval_T *argvars, typval_T *rettv, int inputdialog)
+void get_user_input(const typval_T *const argvars,
+ typval_T *const rettv, const bool inputdialog)
+ FUNC_ATTR_NONNULL_ALL
{
const char *prompt = tv_get_string_chk(&argvars[0]);
int cmd_silent_save = cmd_silent;
@@ -12076,6 +12079,18 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
}
}
+/// luaeval() function implementation
+static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char *const str = (const char *)tv_get_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+
+ executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
+}
+
/*
* "map()" function
*/
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 21b5bbb139..533403b2b0 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -193,6 +193,7 @@ return {
localtime={},
log={args=1, func="float_op_wrapper", data="&log"},
log10={args=1, func="float_op_wrapper", data="&log10"},
+ luaeval={args={1, 2}},
map={args=2},
maparg={args={1, 4}},
mapcheck={args={1, 3}},
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index 8905317f15..935c98fb84 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -11,6 +11,7 @@
#include "nvim/ascii.h"
#include "nvim/macros.h"
#include "nvim/message.h"
+#include "nvim/globals.h"
#include "nvim/charset.h" // vim_str2nr
#include "nvim/lib/kvec.h"
#include "nvim/vim.h" // OK, FAIL
@@ -223,6 +224,78 @@ static inline int json_decoder_pop(ValuesStackItem obj,
} \
} while (0)
+/// Create a new special dictionary that ought to represent a MAP
+///
+/// @param[out] ret_tv Address where new special dictionary is saved.
+///
+/// @return [allocated] list which should contain key-value pairs. Return value
+/// may be safely ignored.
+list_T *decode_create_map_special_dict(typval_T *const ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ list_T *const list = tv_list_alloc();
+ list->lv_refcount++;
+ create_special_dict(ret_tv, kMPMap, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ return list;
+}
+
+/// Convert char* string to typval_T
+///
+/// Depending on whether string has (no) NUL bytes, it may use a special
+/// dictionary or decode string to VAR_STRING.
+///
+/// @param[in] s String to decode.
+/// @param[in] len String length.
+/// @param[in] hasnul Whether string has NUL byte, not or it was not yet
+/// determined.
+/// @param[in] binary If true, save special string type as kMPBinary,
+/// otherwise kMPString.
+/// @param[in] s_allocated If true, then `s` was allocated and can be saved in
+/// a returned structure. If it is not saved there, it
+/// will be freed.
+///
+/// @return Decoded string.
+typval_T decode_string(const char *const s, const size_t len,
+ const TriState hasnul, const bool binary,
+ const bool s_allocated)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ assert(s != NULL || len == 0);
+ const bool really_hasnul = (hasnul == kNone
+ ? memchr(s, NUL, len) != NULL
+ : (bool)hasnul);
+ if (really_hasnul) {
+ list_T *const list = tv_list_alloc();
+ list->lv_refcount++;
+ typval_T tv;
+ create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ const int elw_ret = encode_list_write((void *)list, s, len);
+ if (s_allocated) {
+ xfree((void *)s);
+ }
+ if (elw_ret == -1) {
+ tv_clear(&tv);
+ return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
+ }
+ return tv;
+ } else {
+ return (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_string = (char_u *)(
+ s_allocated ? (char *)s : xmemdupz(s, len)) },
+ };
+ }
+}
+
/// Parse JSON double-quoted string
///
/// @param[in] buf Buffer being converted.
@@ -416,29 +489,13 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len,
}
PUT_FST_IN_PAIR(fst_in_pair, str_end);
#undef PUT_FST_IN_PAIR
- if (hasnul) {
- typval_T obj;
- list_T *const list = tv_list_alloc();
- list->lv_refcount++;
- create_special_dict(&obj, kMPString, ((typval_T) {
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list },
- }));
- if (encode_list_write((void *) list, str, (size_t) (str_end - str))
- == -1) {
- tv_clear(&obj);
- goto parse_json_string_fail;
- }
- xfree(str);
- POP(obj, true);
- } else {
- *str_end = NUL;
- POP(((typval_T) {
- .v_type = VAR_STRING,
- .vval = { .v_string = (char_u *) str },
- }), false);
+ *str_end = NUL;
+ typval_T obj = decode_string(
+ str, (size_t)(str_end - str), hasnul ? kTrue : kFalse, false, true);
+ if (obj.v_type == VAR_UNKNOWN) {
+ goto parse_json_string_fail;
}
+ POP(obj, obj.v_type != VAR_STRING);
goto parse_json_string_ret;
parse_json_string_fail:
ret = FAIL;
@@ -812,13 +869,7 @@ json_decode_string_cycle_start:
list_T *val_list = NULL;
if (next_map_special) {
next_map_special = false;
- val_list = tv_list_alloc();
- val_list->lv_refcount++;
- create_special_dict(&tv, kMPMap, ((typval_T) {
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = val_list },
- }));
+ val_list = decode_create_map_special_dict(&tv);
} else {
dict_T *dict = tv_dict_alloc();
dict->dv_refcount++;
@@ -971,37 +1022,17 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
break;
}
case MSGPACK_OBJECT_STR: {
- list_T *const list = tv_list_alloc();
- list->lv_refcount++;
- create_special_dict(rettv, kMPString, ((typval_T) {
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list },
- }));
- if (encode_list_write((void *) list, mobj.via.str.ptr, mobj.via.str.size)
- == -1) {
+ *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kTrue, false,
+ false);
+ if (rettv->v_type == VAR_UNKNOWN) {
return FAIL;
}
break;
}
case MSGPACK_OBJECT_BIN: {
- if (memchr(mobj.via.bin.ptr, NUL, mobj.via.bin.size) == NULL) {
- *rettv = (typval_T) {
- .v_type = VAR_STRING,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_string = xmemdupz(mobj.via.bin.ptr, mobj.via.bin.size) },
- };
- break;
- }
- list_T *const list = tv_list_alloc();
- list->lv_refcount++;
- create_special_dict(rettv, kMPBinary, ((typval_T) {
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list },
- }));
- if (encode_list_write((void *) list, mobj.via.bin.ptr, mobj.via.bin.size)
- == -1) {
+ *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kNone, true,
+ false);
+ if (rettv->v_type == VAR_UNKNOWN) {
return FAIL;
}
break;
@@ -1058,13 +1089,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
}
break;
msgpack_to_vim_generic_map: {}
- list_T *const list = tv_list_alloc();
- list->lv_refcount++;
- create_special_dict(rettv, kMPMap, ((typval_T) {
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list },
- }));
+ list_T *const list = decode_create_map_special_dict(rettv);
for (size_t i = 0; i < mobj.via.map.size; i++) {
list_T *const kv_pair = tv_list_alloc();
tv_list_append_list(list, kv_pair);
diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h
index c8e7a189e3..77fc4c78c2 100644
--- a/src/nvim/eval/decode.h
+++ b/src/nvim/eval/decode.h
@@ -6,6 +6,7 @@
#include <msgpack.h>
#include "nvim/eval/typval.h"
+#include "nvim/globals.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/decode.h.generated.h"
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index b4a70fb188..a93ad2dbba 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -233,10 +233,6 @@
///
/// This name will only be used by one of the above macros which are defined by
/// the caller. Functions defined here do not use first argument directly.
-#ifndef NVIM_EVAL_TYPVAL_ENCODE_C_H
-#define NVIM_EVAL_TYPVAL_ENCODE_C_H
-#undef NVIM_EVAL_TYPVAL_ENCODE_C_H
-
#include <stddef.h>
#include <inttypes.h>
#include <assert.h>
@@ -816,4 +812,3 @@ encode_vim_to__error_ret:
// Prevent “unused label” warnings.
goto typval_encode_stop_converting_one_item; // -V779
}
-#endif // NVIM_EVAL_TYPVAL_ENCODE_C_H
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 523740444d..7203fbd97d 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -1548,19 +1548,19 @@ return {
command='lua',
flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_script_ni',
+ func='ex_lua',
},
{
command='luado',
flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_ni',
+ func='ex_luado',
},
{
command='luafile',
flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_ni',
+ func='ex_luafile',
},
{
command='lvimgrep',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index a1a32d9f52..1a728647ca 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -3705,19 +3705,18 @@ char_u *get_locales(expand_T *xp, int idx)
static void script_host_execute(char *name, exarg_T *eap)
{
- uint8_t *script = script_get(eap, eap->arg);
+ size_t len;
+ char *const script = script_get(eap, &len);
- if (!eap->skip) {
- list_T *args = tv_list_alloc();
+ if (script != NULL) {
+ list_T *const args = tv_list_alloc();
// script
- tv_list_append_string(args, (const char *)(script ? script : eap->arg), -1);
+ tv_list_append_allocated_string(args, script);
// current range
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
(void)eval_call_provider(name, "execute", args);
}
-
- xfree(script);
}
static void script_host_execute_file(char *name, exarg_T *eap)
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 108b7483b9..cb02befeaf 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -70,6 +70,7 @@
#include "nvim/event/rstream.h"
#include "nvim/event/wstream.h"
#include "nvim/shada.h"
+#include "nvim/lua/executor.h"
#include "nvim/globals.h"
static int quitmore = 0;
@@ -3807,10 +3808,12 @@ void ex_ni(exarg_T *eap)
/// Skips over ":perl <<EOF" constructs.
static void ex_script_ni(exarg_T *eap)
{
- if (!eap->skip)
+ if (!eap->skip) {
ex_ni(eap);
- else
- xfree(script_get(eap, eap->arg));
+ } else {
+ size_t len;
+ xfree(script_get(eap, &len));
+ }
}
/*
@@ -5816,10 +5819,10 @@ int parse_addr_type_arg(char_u *value, int vallen, uint32_t *argt,
* copied to allocated memory and stored in "*compl_arg".
* Returns FAIL if something is wrong.
*/
-int parse_compl_arg(char_u *value, int vallen, int *complp,
+int parse_compl_arg(const char_u *value, int vallen, int *complp,
uint32_t *argt, char_u **compl_arg)
{
- char_u *arg = NULL;
+ const char_u *arg = NULL;
size_t arglen = 0;
int i;
int valend = vallen;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index ff68de7670..fe45ba4568 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -5380,47 +5380,61 @@ static int ex_window(void)
return cmdwin_result;
}
-/*
- * Used for commands that either take a simple command string argument, or:
- * cmd << endmarker
- * {script}
- * endmarker
- * Returns a pointer to allocated memory with {script} or NULL.
- */
-char_u *script_get(exarg_T *eap, char_u *cmd)
+/// Get script string
+///
+/// Used for commands which accept either `:command script` or
+///
+/// :command << endmarker
+/// script
+/// endmarker
+///
+/// @param eap Command being run.
+/// @param[out] lenp Location where length of resulting string is saved. Will
+/// be set to zero when skipping.
+///
+/// @return [allocated] NULL or script. Does not show any error messages.
+/// NULL is returned when skipping and on error.
+char *script_get(exarg_T *const eap, size_t *const lenp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{
- char_u *theline;
- char *end_pattern = NULL;
- char dot[] = ".";
- garray_T ga;
+ const char *const cmd = (const char *)eap->arg;
- if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL)
- return NULL;
-
- ga_init(&ga, 1, 0x400);
+ if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) {
+ *lenp = STRLEN(eap->arg);
+ return xmemdupz(eap->arg, *lenp);
+ }
- if (cmd[2] != NUL)
- end_pattern = (char *)skipwhite(cmd + 2);
- else
- end_pattern = dot;
+ garray_T ga = { .ga_data = NULL, .ga_len = 0 };
+ if (!eap->skip) {
+ ga_init(&ga, 1, 0x400);
+ }
- for (;; ) {
- theline = eap->getline(
+ const char *const end_pattern = (
+ cmd[2] != NUL
+ ? (const char *)skipwhite((const char_u *)cmd + 2)
+ : ".");
+ for (;;) {
+ char *const theline = (char *)eap->getline(
eap->cstack->cs_looplevel > 0 ? -1 :
NUL, eap->cookie, 0);
- if (theline == NULL || STRCMP(end_pattern, theline) == 0) {
+ if (theline == NULL || strcmp(end_pattern, theline) == 0) {
xfree(theline);
break;
}
- ga_concat(&ga, theline);
- ga_append(&ga, '\n');
+ if (!eap->skip) {
+ ga_concat(&ga, (const char_u *)theline);
+ ga_append(&ga, '\n');
+ }
xfree(theline);
}
- ga_append(&ga, NUL);
+ *lenp = (size_t)ga.ga_len; // Set length without trailing NUL.
+ if (!eap->skip) {
+ ga_append(&ga, NUL);
+ }
- return (char_u *)ga.ga_data;
+ return (char *)ga.ga_data;
}
/// Iterate over history items
diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h
index 756c6d05ee..1049b5b7a6 100644
--- a/src/nvim/func_attr.h
+++ b/src/nvim/func_attr.h
@@ -41,10 +41,6 @@
// $ gcc -E -dM - </dev/null
// $ echo | clang -dM -E -
-#ifndef NVIM_FUNC_ATTR_H
-#define NVIM_FUNC_ATTR_H
-#undef NVIM_FUNC_ATTR_H
-
#ifdef FUNC_ATTR_MALLOC
# undef FUNC_ATTR_MALLOC
#endif
@@ -214,4 +210,3 @@
# define FUNC_ATTR_NONNULL_ARG(...)
# define FUNC_ATTR_NONNULL_RET
#endif
-#endif // NVIM_FUNC_ATTR_H
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
new file mode 100644
index 0000000000..7a6167350f
--- /dev/null
+++ b/src/nvim/lua/converter.c
@@ -0,0 +1,1186 @@
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+#include <assert.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/func_attr.h"
+#include "nvim/memory.h"
+#include "nvim/assert.h"
+// FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is
+// redefined
+#include "nvim/vim.h"
+#include "nvim/globals.h"
+#include "nvim/message.h"
+#include "nvim/eval/typval.h"
+#include "nvim/ascii.h"
+#include "nvim/macros.h"
+
+#include "nvim/lib/kvec.h"
+#include "nvim/eval/decode.h"
+
+#include "nvim/lua/converter.h"
+#include "nvim/lua/executor.h"
+
+/// Determine, which keys lua table contains
+typedef struct {
+ size_t maxidx; ///< Maximum positive integral value found.
+ size_t string_keys_num; ///< Number of string keys.
+ bool has_string_with_nul; ///< True if there is string key with NUL byte.
+ ObjectType type; ///< If has_type_key is true then attached value. Otherwise
+ ///< either kObjectTypeNil, kObjectTypeDictionary or
+ ///< kObjectTypeArray, depending on other properties.
+ lua_Number val; ///< If has_val_key and val_type == LUA_TNUMBER: value.
+ bool has_type_key; ///< True if type key is present.
+} LuaTableProps;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/converter.c.generated.h"
+#endif
+
+#define TYPE_IDX_VALUE true
+#define VAL_IDX_VALUE false
+
+#define LUA_PUSH_STATIC_STRING(lstate, s) \
+ lua_pushlstring(lstate, s, sizeof(s) - 1)
+
+static LuaTableProps nlua_traverse_table(lua_State *const lstate)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t tsize = 0; // Total number of keys.
+ int val_type = 0; // If has_val_key: lua type of the value.
+ bool has_val_key = false; // True if val key was found,
+ // @see nlua_push_val_idx().
+ size_t other_keys_num = 0; // Number of keys that are not string, integral
+ // or type keys.
+ LuaTableProps ret;
+ memset(&ret, 0, sizeof(ret));
+ if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) {
+ emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 2);
+ ret.type = kObjectTypeNil;
+ return ret;
+ }
+ lua_pushnil(lstate);
+ while (lua_next(lstate, -2)) {
+ switch (lua_type(lstate, -2)) {
+ case LUA_TSTRING: {
+ size_t len;
+ const char *s = lua_tolstring(lstate, -2, &len);
+ if (memchr(s, NUL, len) != NULL) {
+ ret.has_string_with_nul = true;
+ }
+ ret.string_keys_num++;
+ break;
+ }
+ case LUA_TNUMBER: {
+ const lua_Number n = lua_tonumber(lstate, -2);
+ if (n > (lua_Number)SIZE_MAX || n <= 0
+ || ((lua_Number)((size_t)n)) != n) {
+ other_keys_num++;
+ } else {
+ const size_t idx = (size_t)n;
+ if (idx > ret.maxidx) {
+ ret.maxidx = idx;
+ }
+ }
+ break;
+ }
+ case LUA_TBOOLEAN: {
+ const bool b = lua_toboolean(lstate, -2);
+ if (b == TYPE_IDX_VALUE) {
+ if (lua_type(lstate, -1) == LUA_TNUMBER) {
+ lua_Number n = lua_tonumber(lstate, -1);
+ if (n == (lua_Number)kObjectTypeFloat
+ || n == (lua_Number)kObjectTypeArray
+ || n == (lua_Number)kObjectTypeDictionary) {
+ ret.has_type_key = true;
+ ret.type = (ObjectType)n;
+ } else {
+ other_keys_num++;
+ }
+ } else {
+ other_keys_num++;
+ }
+ } else {
+ has_val_key = true;
+ val_type = lua_type(lstate, -1);
+ if (val_type == LUA_TNUMBER) {
+ ret.val = lua_tonumber(lstate, -1);
+ }
+ }
+ break;
+ }
+ default: {
+ other_keys_num++;
+ break;
+ }
+ }
+ tsize++;
+ lua_pop(lstate, 1);
+ }
+ if (ret.has_type_key) {
+ if (ret.type == kObjectTypeFloat
+ && (!has_val_key || val_type != LUA_TNUMBER)) {
+ ret.type = kObjectTypeNil;
+ } else if (ret.type == kObjectTypeArray) {
+ // Determine what is the last number in a *sequence* of keys.
+ // This condition makes sure that Neovim will not crash when it gets table
+ // {[vim.type_idx]=vim.types.array, [SIZE_MAX]=1}: without it maxidx will
+ // be SIZE_MAX, with this condition it should be zero and [SIZE_MAX] key
+ // should be ignored.
+ if (ret.maxidx != 0
+ && ret.maxidx != (tsize
+ - ret.has_type_key
+ - other_keys_num
+ - has_val_key
+ - ret.string_keys_num)) {
+ for (ret.maxidx = 0;; ret.maxidx++) {
+ lua_rawgeti(lstate, -1, (int)ret.maxidx + 1);
+ if (lua_isnil(lstate, -1)) {
+ lua_pop(lstate, 1);
+ break;
+ }
+ lua_pop(lstate, 1);
+ }
+ }
+ }
+ } else {
+ if (tsize == 0
+ || (tsize == ret.maxidx
+ && other_keys_num == 0
+ && ret.string_keys_num == 0)) {
+ ret.type = kObjectTypeArray;
+ } else if (ret.string_keys_num == tsize) {
+ ret.type = kObjectTypeDictionary;
+ } else {
+ ret.type = kObjectTypeNil;
+ }
+ }
+ return ret;
+}
+
+/// Helper structure for nlua_pop_typval
+typedef struct {
+ typval_T *tv; ///< Location where conversion result is saved.
+ bool container; ///< True if tv is a container.
+ bool special; ///< If true then tv is a _VAL part of special dictionary
+ ///< that represents mapping.
+ int idx; ///< Container index (used to detect self-referencing structures).
+} TVPopStackItem;
+
+/// Convert lua object to VimL typval_T
+///
+/// Should pop exactly one value from lua stack.
+///
+/// @param lstate Lua state.
+/// @param[out] ret_tv Where to put the result.
+///
+/// @return `true` in case of success, `false` in case of failure. Error is
+/// reported automatically.
+bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
+{
+ bool ret = true;
+ const int initial_size = lua_gettop(lstate);
+ kvec_t(TVPopStackItem) stack = KV_INITIAL_VALUE;
+ kv_push(stack, ((TVPopStackItem) { ret_tv, false, false, 0 }));
+ while (ret && kv_size(stack)) {
+ if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) {
+ emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 3);
+ ret = false;
+ break;
+ }
+ TVPopStackItem cur = kv_pop(stack);
+ if (cur.container) {
+ if (cur.special || cur.tv->v_type == VAR_DICT) {
+ assert(cur.tv->v_type == (cur.special ? VAR_LIST : VAR_DICT));
+ bool next_key_found = false;
+ while (lua_next(lstate, -2)) {
+ if (lua_type(lstate, -2) == LUA_TSTRING) {
+ next_key_found = true;
+ break;
+ }
+ lua_pop(lstate, 1);
+ }
+ if (next_key_found) {
+ size_t len;
+ const char *s = lua_tolstring(lstate, -2, &len);
+ if (cur.special) {
+ list_T *const kv_pair = tv_list_alloc();
+ tv_list_append_list(cur.tv->vval.v_list, kv_pair);
+ listitem_T *const key = tv_list_item_alloc();
+ key->li_tv = decode_string(s, len, kTrue, false, false);
+ tv_list_append(kv_pair, key);
+ if (key->li_tv.v_type == VAR_UNKNOWN) {
+ ret = false;
+ tv_list_unref(kv_pair);
+ continue;
+ }
+ listitem_T *const val = tv_list_item_alloc();
+ tv_list_append(kv_pair, val);
+ kv_push(stack, cur);
+ cur = (TVPopStackItem) { &val->li_tv, false, false, 0 };
+ } else {
+ dictitem_T *const di = tv_dict_item_alloc_len(s, len);
+ if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) {
+ assert(false);
+ }
+ kv_push(stack, cur);
+ cur = (TVPopStackItem) { &di->di_tv, false, false, 0 };
+ }
+ } else {
+ lua_pop(lstate, 1);
+ continue;
+ }
+ } else {
+ assert(cur.tv->v_type == VAR_LIST);
+ lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1);
+ if (lua_isnil(lstate, -1)) {
+ lua_pop(lstate, 2);
+ continue;
+ }
+ listitem_T *const li = tv_list_item_alloc();
+ tv_list_append(cur.tv->vval.v_list, li);
+ kv_push(stack, cur);
+ cur = (TVPopStackItem) { &li->li_tv, false, false, 0 };
+ }
+ }
+ assert(!cur.container);
+ *cur.tv = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_number = 0 },
+ };
+ switch (lua_type(lstate, -1)) {
+ case LUA_TNIL: {
+ cur.tv->v_type = VAR_SPECIAL;
+ cur.tv->vval.v_special = kSpecialVarNull;
+ break;
+ }
+ case LUA_TBOOLEAN: {
+ cur.tv->v_type = VAR_SPECIAL;
+ cur.tv->vval.v_special = (lua_toboolean(lstate, -1)
+ ? kSpecialVarTrue
+ : kSpecialVarFalse);
+ break;
+ }
+ case LUA_TSTRING: {
+ size_t len;
+ const char *s = lua_tolstring(lstate, -1, &len);
+ *cur.tv = decode_string(s, len, kNone, true, false);
+ if (cur.tv->v_type == VAR_UNKNOWN) {
+ ret = false;
+ }
+ break;
+ }
+ case LUA_TNUMBER: {
+ const lua_Number n = lua_tonumber(lstate, -1);
+ if (n > (lua_Number)VARNUMBER_MAX || n < (lua_Number)VARNUMBER_MIN
+ || ((lua_Number)((varnumber_T)n)) != n) {
+ cur.tv->v_type = VAR_FLOAT;
+ cur.tv->vval.v_float = (float_T)n;
+ } else {
+ cur.tv->v_type = VAR_NUMBER;
+ cur.tv->vval.v_number = (varnumber_T)n;
+ }
+ break;
+ }
+ case LUA_TTABLE: {
+ const LuaTableProps table_props = nlua_traverse_table(lstate);
+
+ for (size_t i = 0; i < kv_size(stack); i++) {
+ const TVPopStackItem item = kv_A(stack, i);
+ if (item.container && lua_rawequal(lstate, -1, item.idx)) {
+ tv_copy(item.tv, cur.tv);
+ cur.container = false;
+ goto nlua_pop_typval_table_processing_end;
+ }
+ }
+
+ switch (table_props.type) {
+ case kObjectTypeArray: {
+ cur.tv->v_type = VAR_LIST;
+ cur.tv->vval.v_list = tv_list_alloc();
+ cur.tv->vval.v_list->lv_refcount++;
+ if (table_props.maxidx != 0) {
+ cur.container = true;
+ cur.idx = lua_gettop(lstate);
+ kv_push(stack, cur);
+ }
+ break;
+ }
+ case kObjectTypeDictionary: {
+ if (table_props.string_keys_num == 0) {
+ cur.tv->v_type = VAR_DICT;
+ cur.tv->vval.v_dict = tv_dict_alloc();
+ cur.tv->vval.v_dict->dv_refcount++;
+ } else {
+ cur.special = table_props.has_string_with_nul;
+ if (table_props.has_string_with_nul) {
+ decode_create_map_special_dict(cur.tv);
+ assert(cur.tv->v_type = VAR_DICT);
+ dictitem_T *const val_di = tv_dict_find(cur.tv->vval.v_dict,
+ S_LEN("_VAL"));
+ assert(val_di != NULL);
+ cur.tv = &val_di->di_tv;
+ assert(cur.tv->v_type == VAR_LIST);
+ } else {
+ cur.tv->v_type = VAR_DICT;
+ cur.tv->vval.v_dict = tv_dict_alloc();
+ cur.tv->vval.v_dict->dv_refcount++;
+ }
+ cur.container = true;
+ cur.idx = lua_gettop(lstate);
+ kv_push(stack, cur);
+ lua_pushnil(lstate);
+ }
+ break;
+ }
+ case kObjectTypeFloat: {
+ cur.tv->v_type = VAR_FLOAT;
+ cur.tv->vval.v_float = (float_T)table_props.val;
+ break;
+ }
+ case kObjectTypeNil: {
+ EMSG(_("E5100: Cannot convert given lua table: table "
+ "should either have a sequence of positive integer keys "
+ "or contain only string keys"));
+ ret = false;
+ break;
+ }
+ default: {
+ assert(false);
+ }
+ }
+nlua_pop_typval_table_processing_end:
+ break;
+ }
+ default: {
+ EMSG(_("E5101: Cannot convert given lua type"));
+ ret = false;
+ break;
+ }
+ }
+ if (!cur.container) {
+ lua_pop(lstate, 1);
+ }
+ }
+ kv_destroy(stack);
+ if (!ret) {
+ tv_clear(ret_tv);
+ *ret_tv = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_number = 0 },
+ };
+ lua_pop(lstate, lua_gettop(lstate) - initial_size + 1);
+ }
+ assert(lua_gettop(lstate) == initial_size - 1);
+ return ret;
+}
+
+#define TYPVAL_ENCODE_ALLOW_SPECIALS true
+
+#define TYPVAL_ENCODE_CONV_NIL(tv) \
+ lua_pushnil(lstate)
+
+#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
+ lua_pushboolean(lstate, (bool)(num))
+
+#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
+ lua_pushnumber(lstate, (lua_Number)(num))
+
+#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER
+
+#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
+ TYPVAL_ENCODE_CONV_NUMBER(tv, flt)
+
+#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
+ lua_pushlstring(lstate, (const char *)(str), (len))
+
+#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
+
+#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
+ TYPVAL_ENCODE_CONV_NIL()
+
+#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
+ do { \
+ TYPVAL_ENCODE_CONV_NIL(tv); \
+ goto typval_encode_stop_converting_one_item; \
+ } while (0)
+
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_END(tv)
+
+#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
+ lua_createtable(lstate, 0, 0)
+
+#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
+ nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary)
+
+#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
+ do { \
+ if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \
+ emsgf(_("E5102: Lua failed to grow stack to %i"), \
+ lua_gettop(lstate) + 3); \
+ return false; \
+ } \
+ lua_createtable(lstate, (int)(len), 0); \
+ lua_pushnumber(lstate, 1); \
+ } while (0)
+
+#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv)
+
+#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \
+ do { \
+ lua_Number idx = lua_tonumber(lstate, -2); \
+ lua_rawset(lstate, -3); \
+ lua_pushnumber(lstate, idx + 1); \
+ } while (0)
+
+#define TYPVAL_ENCODE_CONV_LIST_END(tv) \
+ lua_rawset(lstate, -3)
+
+#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
+ do { \
+ if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \
+ emsgf(_("E5102: Lua failed to grow stack to %i"), \
+ lua_gettop(lstate) + 3); \
+ return false; \
+ } \
+ lua_createtable(lstate, 0, (int)(len)); \
+ } while (0)
+
+#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair)
+
+#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv)
+
+#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict)
+
+#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \
+ lua_rawset(lstate, -3)
+
+#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \
+ TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict)
+
+#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
+ do { \
+ for (size_t backref = kv_size(*mpstack); backref; backref--) { \
+ const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \
+ if (mpval.type == conv_type) { \
+ if (conv_type == kMPConvDict \
+ ? (void *)mpval.data.d.dict == (void *)(val) \
+ : (void *)mpval.data.l.list == (void *)(val)) { \
+ lua_pushvalue(lstate, \
+ -((int)((kv_size(*mpstack) - backref + 1) * 2))); \
+ break; \
+ } \
+ } \
+ } \
+ } while (0)
+
+#define TYPVAL_ENCODE_SCOPE static
+#define TYPVAL_ENCODE_NAME lua
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE lua_State *const
+#define TYPVAL_ENCODE_FIRST_ARG_NAME lstate
+#include "nvim/eval/typval_encode.c.h"
+#undef TYPVAL_ENCODE_SCOPE
+#undef TYPVAL_ENCODE_NAME
+#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
+#undef TYPVAL_ENCODE_FIRST_ARG_NAME
+
+#undef TYPVAL_ENCODE_CONV_STRING
+#undef TYPVAL_ENCODE_CONV_STR_STRING
+#undef TYPVAL_ENCODE_CONV_EXT_STRING
+#undef TYPVAL_ENCODE_CONV_NUMBER
+#undef TYPVAL_ENCODE_CONV_FLOAT
+#undef TYPVAL_ENCODE_CONV_FUNC_START
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
+#undef TYPVAL_ENCODE_CONV_FUNC_END
+#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
+#undef TYPVAL_ENCODE_CONV_LIST_START
+#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
+#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
+#undef TYPVAL_ENCODE_CONV_NIL
+#undef TYPVAL_ENCODE_CONV_BOOL
+#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
+#undef TYPVAL_ENCODE_CONV_DICT_START
+#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
+#undef TYPVAL_ENCODE_CONV_DICT_END
+#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
+#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
+#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
+#undef TYPVAL_ENCODE_CONV_LIST_END
+#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
+#undef TYPVAL_ENCODE_CONV_RECURSE
+#undef TYPVAL_ENCODE_ALLOW_SPECIALS
+
+/// Convert VimL typval_T to lua value
+///
+/// Should leave single value in lua stack. May only fail if lua failed to grow
+/// stack.
+///
+/// @param lstate Lua interpreter state.
+/// @param[in] tv typval_T to convert.
+///
+/// @return true in case of success, false otherwise.
+bool nlua_push_typval(lua_State *lstate, typval_T *const tv)
+{
+ const int initial_size = lua_gettop(lstate);
+ if (!lua_checkstack(lstate, initial_size + 2)) {
+ emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4);
+ return false;
+ }
+ if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) {
+ return false;
+ }
+ assert(lua_gettop(lstate) == initial_size + 1);
+ return true;
+}
+
+/// Push value which is a type index
+///
+/// Used for all “typed” tables: i.e. for all tables which represent VimL
+/// values.
+static inline void nlua_push_type_idx(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_pushboolean(lstate, TYPE_IDX_VALUE);
+}
+
+/// Push value which is a value index
+///
+/// Used for tables which represent scalar values, like float value.
+static inline void nlua_push_val_idx(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_pushboolean(lstate, VAL_IDX_VALUE);
+}
+
+/// Push type
+///
+/// Type is a value in vim.types table.
+///
+/// @param[out] lstate Lua state.
+/// @param[in] type Type to push.
+static inline void nlua_push_type(lua_State *lstate, ObjectType type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_pushnumber(lstate, (lua_Number)type);
+}
+
+/// Create lua table which has an entry that determines its VimL type
+///
+/// @param[out] lstate Lua state.
+/// @param[in] narr Number of “array” entries to be populated later.
+/// @param[in] nrec Number of “dictionary” entries to be populated later.
+/// @param[in] type Type of the table.
+static inline void nlua_create_typed_table(lua_State *lstate,
+ const size_t narr,
+ const size_t nrec,
+ const ObjectType type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_createtable(lstate, (int)narr, (int)(1 + nrec));
+ nlua_push_type_idx(lstate);
+ nlua_push_type(lstate, type);
+ lua_rawset(lstate, -3);
+}
+
+
+/// Convert given String to lua string
+///
+/// Leaves converted string on top of the stack.
+void nlua_push_String(lua_State *lstate, const String s)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_pushlstring(lstate, s.data, s.size);
+}
+
+/// Convert given Integer to lua number
+///
+/// Leaves converted number on top of the stack.
+void nlua_push_Integer(lua_State *lstate, const Integer n)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_pushnumber(lstate, (lua_Number)n);
+}
+
+/// Convert given Float to lua table
+///
+/// Leaves converted table on top of the stack.
+void nlua_push_Float(lua_State *lstate, const Float f)
+ FUNC_ATTR_NONNULL_ALL
+{
+ nlua_create_typed_table(lstate, 0, 1, kObjectTypeFloat);
+ nlua_push_val_idx(lstate);
+ lua_pushnumber(lstate, (lua_Number)f);
+ lua_rawset(lstate, -3);
+}
+
+/// Convert given Float to lua boolean
+///
+/// Leaves converted value on top of the stack.
+void nlua_push_Boolean(lua_State *lstate, const Boolean b)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_pushboolean(lstate, b);
+}
+
+/// Convert given Dictionary to lua table
+///
+/// Leaves converted table on top of the stack.
+void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (dict.size == 0) {
+ nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary);
+ } else {
+ lua_createtable(lstate, 0, (int)dict.size);
+ }
+ for (size_t i = 0; i < dict.size; i++) {
+ nlua_push_String(lstate, dict.items[i].key);
+ nlua_push_Object(lstate, dict.items[i].value);
+ lua_rawset(lstate, -3);
+ }
+}
+
+/// Convert given Array to lua table
+///
+/// Leaves converted table on top of the stack.
+void nlua_push_Array(lua_State *lstate, const Array array)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_createtable(lstate, (int)array.size, 0);
+ for (size_t i = 0; i < array.size; i++) {
+ nlua_push_Object(lstate, array.items[i]);
+ lua_rawseti(lstate, -2, (int)i + 1);
+ }
+}
+
+#define GENERATE_INDEX_FUNCTION(type) \
+void nlua_push_##type(lua_State *lstate, const type item) \
+ FUNC_ATTR_NONNULL_ALL \
+{ \
+ lua_pushnumber(lstate, (lua_Number)(item)); \
+}
+
+GENERATE_INDEX_FUNCTION(Buffer)
+GENERATE_INDEX_FUNCTION(Window)
+GENERATE_INDEX_FUNCTION(Tabpage)
+
+#undef GENERATE_INDEX_FUNCTION
+
+/// Convert given Object to lua value
+///
+/// Leaves converted value on top of the stack.
+void nlua_push_Object(lua_State *lstate, const Object obj)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (obj.type) {
+ case kObjectTypeNil: {
+ lua_pushnil(lstate);
+ break;
+ }
+#define ADD_TYPE(type, data_key) \
+ case kObjectType##type: { \
+ nlua_push_##type(lstate, obj.data.data_key); \
+ break; \
+ }
+ ADD_TYPE(Boolean, boolean)
+ ADD_TYPE(Integer, integer)
+ ADD_TYPE(Float, floating)
+ ADD_TYPE(String, string)
+ ADD_TYPE(Array, array)
+ ADD_TYPE(Dictionary, dictionary)
+#undef ADD_TYPE
+#define ADD_REMOTE_TYPE(type) \
+ case kObjectType##type: { \
+ nlua_push_##type(lstate, (type)obj.data.integer); \
+ break; \
+ }
+ ADD_REMOTE_TYPE(Buffer)
+ ADD_REMOTE_TYPE(Window)
+ ADD_REMOTE_TYPE(Tabpage)
+#undef ADD_REMOTE_TYPE
+ }
+}
+
+
+/// Convert lua value to string
+///
+/// Always pops one value from the stack.
+String nlua_pop_String(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (lua_type(lstate, -1) != LUA_TSTRING) {
+ lua_pop(lstate, 1);
+ api_set_error(err, kErrorTypeValidation, "Expected lua string");
+ return (String) { .size = 0, .data = NULL };
+ }
+ String ret;
+
+ ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size));
+ assert(ret.data != NULL);
+ ret.data = xmemdupz(ret.data, ret.size);
+ lua_pop(lstate, 1);
+
+ return ret;
+}
+
+/// Convert lua value to integer
+///
+/// Always pops one value from the stack.
+Integer nlua_pop_Integer(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (lua_type(lstate, -1) != LUA_TNUMBER) {
+ lua_pop(lstate, 1);
+ api_set_error(err, kErrorTypeValidation, "Expected lua number");
+ return 0;
+ }
+ const lua_Number n = lua_tonumber(lstate, -1);
+ lua_pop(lstate, 1);
+ if (n > (lua_Number)API_INTEGER_MAX || n < (lua_Number)API_INTEGER_MIN
+ || ((lua_Number)((Integer)n)) != n) {
+ api_set_error(err, kErrorTypeException, "Number is not integral");
+ return 0;
+ }
+ return (Integer)n;
+}
+
+/// Convert lua value to boolean
+///
+/// Always pops one value from the stack.
+Boolean nlua_pop_Boolean(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const Boolean ret = lua_toboolean(lstate, -1);
+ lua_pop(lstate, 1);
+ return ret;
+}
+
+/// Check whether typed table on top of the stack has given type
+///
+/// @param[in] lstate Lua state.
+/// @param[out] err Location where error will be saved. May be NULL.
+/// @param[in] type Type to check.
+///
+/// @return @see nlua_traverse_table().
+static inline LuaTableProps nlua_check_type(lua_State *const lstate,
+ Error *const err,
+ const ObjectType type)
+ FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (lua_type(lstate, -1) != LUA_TTABLE) {
+ if (err) {
+ api_set_error(err, kErrorTypeValidation, "Expected lua table");
+ }
+ return (LuaTableProps) { .type = kObjectTypeNil };
+ }
+ LuaTableProps table_props = nlua_traverse_table(lstate);
+
+ if (type == kObjectTypeDictionary && table_props.type == kObjectTypeArray
+ && table_props.maxidx == 0 && !table_props.has_type_key) {
+ table_props.type = kObjectTypeDictionary;
+ }
+
+ if (table_props.type != type) {
+ if (err) {
+ api_set_error(err, kErrorTypeValidation, "Unexpected type");
+ }
+ }
+
+ return table_props;
+}
+
+/// Convert lua table to float
+///
+/// Always pops one value from the stack.
+Float nlua_pop_Float(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (lua_type(lstate, -1) == LUA_TNUMBER) {
+ const Float ret = (Float)lua_tonumber(lstate, -1);
+ lua_pop(lstate, 1);
+ return ret;
+ }
+
+ const LuaTableProps table_props = nlua_check_type(lstate, err,
+ kObjectTypeFloat);
+ lua_pop(lstate, 1);
+ if (table_props.type != kObjectTypeFloat) {
+ return 0;
+ } else {
+ return (Float)table_props.val;
+ }
+}
+
+/// Convert lua table to array without determining whether it is array
+///
+/// @param lstate Lua state.
+/// @param[in] table_props nlua_traverse_table() output.
+/// @param[out] err Location where error will be saved.
+static Array nlua_pop_Array_unchecked(lua_State *const lstate,
+ const LuaTableProps table_props,
+ Error *const err)
+{
+ Array ret = { .size = table_props.maxidx, .items = NULL };
+
+ if (ret.size == 0) {
+ lua_pop(lstate, 1);
+ return ret;
+ }
+
+ ret.items = xcalloc(ret.size, sizeof(*ret.items));
+ for (size_t i = 1; i <= ret.size; i++) {
+ Object val;
+
+ lua_rawgeti(lstate, -1, (int)i);
+
+ val = nlua_pop_Object(lstate, err);
+ if (ERROR_SET(err)) {
+ ret.size = i - 1;
+ lua_pop(lstate, 1);
+ api_free_array(ret);
+ return (Array) { .size = 0, .items = NULL };
+ }
+ ret.items[i - 1] = val;
+ }
+ lua_pop(lstate, 1);
+
+ return ret;
+}
+
+/// Convert lua table to array
+///
+/// Always pops one value from the stack.
+Array nlua_pop_Array(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const LuaTableProps table_props = nlua_check_type(lstate, err,
+ kObjectTypeArray);
+ if (table_props.type != kObjectTypeArray) {
+ return (Array) { .size = 0, .items = NULL };
+ }
+ return nlua_pop_Array_unchecked(lstate, table_props, err);
+}
+
+/// Convert lua table to dictionary
+///
+/// Always pops one value from the stack. Does not check whether whether topmost
+/// value on the stack is a table.
+///
+/// @param lstate Lua interpreter state.
+/// @param[in] table_props nlua_traverse_table() output.
+/// @param[out] err Location where error will be saved.
+static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate,
+ const LuaTableProps table_props,
+ Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ Dictionary ret = { .size = table_props.string_keys_num, .items = NULL };
+
+ if (ret.size == 0) {
+ lua_pop(lstate, 1);
+ return ret;
+ }
+ ret.items = xcalloc(ret.size, sizeof(*ret.items));
+
+ lua_pushnil(lstate);
+ for (size_t i = 0; lua_next(lstate, -2) && i < ret.size;) {
+ // stack: dict, key, value
+
+ if (lua_type(lstate, -2) == LUA_TSTRING) {
+ lua_pushvalue(lstate, -2);
+ // stack: dict, key, value, key
+
+ ret.items[i].key = nlua_pop_String(lstate, err);
+ // stack: dict, key, value
+
+ if (!ERROR_SET(err)) {
+ ret.items[i].value = nlua_pop_Object(lstate, err);
+ // stack: dict, key
+ } else {
+ lua_pop(lstate, 1);
+ // stack: dict, key
+ }
+
+ if (ERROR_SET(err)) {
+ ret.size = i;
+ api_free_dictionary(ret);
+ lua_pop(lstate, 2);
+ // stack:
+ return (Dictionary) { .size = 0, .items = NULL };
+ }
+ i++;
+ } else {
+ lua_pop(lstate, 1);
+ // stack: dict, key
+ }
+ }
+ lua_pop(lstate, 1);
+
+ return ret;
+}
+
+/// Convert lua table to dictionary
+///
+/// Always pops one value from the stack.
+Dictionary nlua_pop_Dictionary(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const LuaTableProps table_props = nlua_check_type(lstate, err,
+ kObjectTypeDictionary);
+ if (table_props.type != kObjectTypeDictionary) {
+ lua_pop(lstate, 1);
+ return (Dictionary) { .size = 0, .items = NULL };
+ }
+
+ return nlua_pop_Dictionary_unchecked(lstate, table_props, err);
+}
+
+/// Helper structure for nlua_pop_Object
+typedef struct {
+ Object *obj; ///< Location where conversion result is saved.
+ bool container; ///< True if tv is a container.
+} ObjPopStackItem;
+
+/// Convert lua table to object
+///
+/// Always pops one value from the stack.
+Object nlua_pop_Object(lua_State *const lstate, Error *const err)
+{
+ Object ret = NIL;
+ const int initial_size = lua_gettop(lstate);
+ kvec_t(ObjPopStackItem) stack = KV_INITIAL_VALUE;
+ kv_push(stack, ((ObjPopStackItem) { &ret, false }));
+ while (!ERROR_SET(err) && kv_size(stack)) {
+ if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) {
+ api_set_error(err, kErrorTypeException, "Lua failed to grow stack");
+ break;
+ }
+ ObjPopStackItem cur = kv_pop(stack);
+ if (cur.container) {
+ if (cur.obj->type == kObjectTypeDictionary) {
+ // stack: …, dict, key
+ if (cur.obj->data.dictionary.size
+ == cur.obj->data.dictionary.capacity) {
+ lua_pop(lstate, 2);
+ continue;
+ }
+ bool next_key_found = false;
+ while (lua_next(lstate, -2)) {
+ // stack: …, dict, new key, val
+ if (lua_type(lstate, -2) == LUA_TSTRING) {
+ next_key_found = true;
+ break;
+ }
+ lua_pop(lstate, 1);
+ // stack: …, dict, new key
+ }
+ if (next_key_found) {
+ // stack: …, dict, new key, val
+ size_t len;
+ const char *s = lua_tolstring(lstate, -2, &len);
+ const size_t idx = cur.obj->data.dictionary.size++;
+ cur.obj->data.dictionary.items[idx].key = (String) {
+ .data = xmemdupz(s, len),
+ .size = len,
+ };
+ kv_push(stack, cur);
+ cur = (ObjPopStackItem) {
+ .obj = &cur.obj->data.dictionary.items[idx].value,
+ .container = false,
+ };
+ } else {
+ // stack: …, dict
+ lua_pop(lstate, 1);
+ // stack: …
+ continue;
+ }
+ } else {
+ if (cur.obj->data.array.size == cur.obj->data.array.capacity) {
+ lua_pop(lstate, 1);
+ continue;
+ }
+ const size_t idx = cur.obj->data.array.size++;
+ lua_rawgeti(lstate, -1, (int)idx + 1);
+ if (lua_isnil(lstate, -1)) {
+ lua_pop(lstate, 2);
+ continue;
+ }
+ kv_push(stack, cur);
+ cur = (ObjPopStackItem) {
+ .obj = &cur.obj->data.array.items[idx],
+ .container = false,
+ };
+ }
+ }
+ assert(!cur.container);
+ *cur.obj = NIL;
+ switch (lua_type(lstate, -1)) {
+ case LUA_TNIL: {
+ break;
+ }
+ case LUA_TBOOLEAN: {
+ *cur.obj = BOOLEAN_OBJ(lua_toboolean(lstate, -1));
+ break;
+ }
+ case LUA_TSTRING: {
+ size_t len;
+ const char *s = lua_tolstring(lstate, -1, &len);
+ *cur.obj = STRING_OBJ(((String) {
+ .data = xmemdupz(s, len),
+ .size = len,
+ }));
+ break;
+ }
+ case LUA_TNUMBER: {
+ const lua_Number n = lua_tonumber(lstate, -1);
+ if (n > (lua_Number)API_INTEGER_MAX || n < (lua_Number)API_INTEGER_MIN
+ || ((lua_Number)((Integer)n)) != n) {
+ *cur.obj = FLOAT_OBJ((Float)n);
+ } else {
+ *cur.obj = INTEGER_OBJ((Integer)n);
+ }
+ break;
+ }
+ case LUA_TTABLE: {
+ const LuaTableProps table_props = nlua_traverse_table(lstate);
+
+ switch (table_props.type) {
+ case kObjectTypeArray: {
+ *cur.obj = ARRAY_OBJ(((Array) {
+ .items = NULL,
+ .size = 0,
+ .capacity = 0,
+ }));
+ if (table_props.maxidx != 0) {
+ cur.obj->data.array.items =
+ xcalloc(table_props.maxidx,
+ sizeof(cur.obj->data.array.items[0]));
+ cur.obj->data.array.capacity = table_props.maxidx;
+ cur.container = true;
+ kv_push(stack, cur);
+ }
+ break;
+ }
+ case kObjectTypeDictionary: {
+ *cur.obj = DICTIONARY_OBJ(((Dictionary) {
+ .items = NULL,
+ .size = 0,
+ .capacity = 0,
+ }));
+ if (table_props.string_keys_num != 0) {
+ cur.obj->data.dictionary.items =
+ xcalloc(table_props.string_keys_num,
+ sizeof(cur.obj->data.dictionary.items[0]));
+ cur.obj->data.dictionary.capacity = table_props.string_keys_num;
+ cur.container = true;
+ kv_push(stack, cur);
+ lua_pushnil(lstate);
+ }
+ break;
+ }
+ case kObjectTypeFloat: {
+ *cur.obj = FLOAT_OBJ((Float)table_props.val);
+ break;
+ }
+ case kObjectTypeNil: {
+ api_set_error(err, kErrorTypeValidation,
+ "Cannot convert given lua table");
+ break;
+ }
+ default: {
+ assert(false);
+ }
+ }
+ break;
+ }
+ default: {
+ api_set_error(err, kErrorTypeValidation,
+ "Cannot convert given lua type");
+ break;
+ }
+ }
+ if (!cur.container) {
+ lua_pop(lstate, 1);
+ }
+ }
+ kv_destroy(stack);
+ if (ERROR_SET(err)) {
+ api_free_object(ret);
+ ret = NIL;
+ lua_pop(lstate, lua_gettop(lstate) - initial_size + 1);
+ }
+ assert(lua_gettop(lstate) == initial_size - 1);
+ return ret;
+}
+
+#define GENERATE_INDEX_FUNCTION(type) \
+type nlua_pop_##type(lua_State *lstate, Error *err) \
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
+{ \
+ type ret; \
+ ret = (type)lua_tonumber(lstate, -1); \
+ lua_pop(lstate, 1); \
+ return ret; \
+}
+
+GENERATE_INDEX_FUNCTION(Buffer)
+GENERATE_INDEX_FUNCTION(Window)
+GENERATE_INDEX_FUNCTION(Tabpage)
+
+#undef GENERATE_INDEX_FUNCTION
+
+/// Record some auxilary values in vim module
+///
+/// Assumes that module table is on top of the stack.
+///
+/// Recorded values:
+///
+/// `vim.type_idx`: @see nlua_push_type_idx()
+/// `vim.val_idx`: @see nlua_push_val_idx()
+/// `vim.types`: table mapping possible values of `vim.type_idx` to string
+/// names (i.e. `array`, `float`, `dictionary`) and back.
+void nlua_init_types(lua_State *const lstate)
+{
+ LUA_PUSH_STATIC_STRING(lstate, "type_idx");
+ nlua_push_type_idx(lstate);
+ lua_rawset(lstate, -3);
+
+ LUA_PUSH_STATIC_STRING(lstate, "val_idx");
+ nlua_push_val_idx(lstate);
+ lua_rawset(lstate, -3);
+
+ LUA_PUSH_STATIC_STRING(lstate, "types");
+ lua_createtable(lstate, 0, 3);
+
+ LUA_PUSH_STATIC_STRING(lstate, "float");
+ lua_pushnumber(lstate, (lua_Number)kObjectTypeFloat);
+ lua_rawset(lstate, -3);
+ lua_pushnumber(lstate, (lua_Number)kObjectTypeFloat);
+ LUA_PUSH_STATIC_STRING(lstate, "float");
+ lua_rawset(lstate, -3);
+
+ LUA_PUSH_STATIC_STRING(lstate, "array");
+ lua_pushnumber(lstate, (lua_Number)kObjectTypeArray);
+ lua_rawset(lstate, -3);
+ lua_pushnumber(lstate, (lua_Number)kObjectTypeArray);
+ LUA_PUSH_STATIC_STRING(lstate, "array");
+ lua_rawset(lstate, -3);
+
+ LUA_PUSH_STATIC_STRING(lstate, "dictionary");
+ lua_pushnumber(lstate, (lua_Number)kObjectTypeDictionary);
+ lua_rawset(lstate, -3);
+ lua_pushnumber(lstate, (lua_Number)kObjectTypeDictionary);
+ LUA_PUSH_STATIC_STRING(lstate, "dictionary");
+ lua_rawset(lstate, -3);
+
+ lua_rawset(lstate, -3);
+}
diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h
new file mode 100644
index 0000000000..542c56ea3e
--- /dev/null
+++ b/src/nvim/lua/converter.h
@@ -0,0 +1,15 @@
+#ifndef NVIM_LUA_CONVERTER_H
+#define NVIM_LUA_CONVERTER_H
+
+#include <lua.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/func_attr.h"
+#include "nvim/eval.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/converter.h.generated.h"
+#endif
+#endif // NVIM_LUA_CONVERTER_H
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
new file mode 100644
index 0000000000..7cf326aef5
--- /dev/null
+++ b/src/nvim/lua/executor.c
@@ -0,0 +1,576 @@
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "nvim/misc1.h"
+#include "nvim/getchar.h"
+#include "nvim/garray.h"
+#include "nvim/func_attr.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/vim.h"
+#include "nvim/vim.h"
+#include "nvim/ex_getln.h"
+#include "nvim/message.h"
+#include "nvim/memline.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/macros.h"
+#include "nvim/screen.h"
+#include "nvim/cursor.h"
+#include "nvim/undo.h"
+#include "nvim/ascii.h"
+
+#include "nvim/lua/executor.h"
+#include "nvim/lua/converter.h"
+
+typedef struct {
+ Error err;
+ String lua_err_str;
+} LuaError;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/vim_module.generated.h"
+# include "lua/executor.c.generated.h"
+#endif
+
+/// Name of the run code for use in messages
+#define NLUA_EVAL_NAME "<VimL compiled string>"
+
+/// Call C function which does not expect any arguments
+///
+/// @param function Called function
+/// @param numret Number of returned arguments
+#define NLUA_CALL_C_FUNCTION_0(lstate, function, numret) \
+ do { \
+ lua_pushcfunction(lstate, &function); \
+ lua_call(lstate, 0, numret); \
+ } while (0)
+/// Call C function which expects one argument
+///
+/// @param function Called function
+/// @param numret Number of returned arguments
+/// @param a… Supplied argument (should be a void* pointer)
+#define NLUA_CALL_C_FUNCTION_1(lstate, function, numret, a1) \
+ do { \
+ lua_pushcfunction(lstate, &function); \
+ lua_pushlightuserdata(lstate, a1); \
+ lua_call(lstate, 1, numret); \
+ } while (0)
+/// Call C function which expects two arguments
+///
+/// @param function Called function
+/// @param numret Number of returned arguments
+/// @param a… Supplied argument (should be a void* pointer)
+#define NLUA_CALL_C_FUNCTION_2(lstate, function, numret, a1, a2) \
+ do { \
+ lua_pushcfunction(lstate, &function); \
+ lua_pushlightuserdata(lstate, a1); \
+ lua_pushlightuserdata(lstate, a2); \
+ lua_call(lstate, 2, numret); \
+ } while (0)
+/// Call C function which expects three arguments
+///
+/// @param function Called function
+/// @param numret Number of returned arguments
+/// @param a… Supplied argument (should be a void* pointer)
+#define NLUA_CALL_C_FUNCTION_3(lstate, function, numret, a1, a2, a3) \
+ do { \
+ lua_pushcfunction(lstate, &function); \
+ lua_pushlightuserdata(lstate, a1); \
+ lua_pushlightuserdata(lstate, a2); \
+ lua_pushlightuserdata(lstate, a3); \
+ lua_call(lstate, 3, numret); \
+ } while (0)
+/// Call C function which expects five arguments
+///
+/// @param function Called function
+/// @param numret Number of returned arguments
+/// @param a… Supplied argument (should be a void* pointer)
+#define NLUA_CALL_C_FUNCTION_4(lstate, function, numret, a1, a2, a3, a4) \
+ do { \
+ lua_pushcfunction(lstate, &function); \
+ lua_pushlightuserdata(lstate, a1); \
+ lua_pushlightuserdata(lstate, a2); \
+ lua_pushlightuserdata(lstate, a3); \
+ lua_pushlightuserdata(lstate, a4); \
+ lua_call(lstate, 4, numret); \
+ } while (0)
+
+/// Convert lua error into a Vim error message
+///
+/// @param lstate Lua interpreter state.
+/// @param[in] msg Message base, must contain one `%s`.
+static void nlua_error(lua_State *const lstate, const char *const msg)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t len;
+ const char *const str = lua_tolstring(lstate, -1, &len);
+
+ emsgf(msg, (int)len, str);
+
+ lua_pop(lstate, 1);
+}
+
+/// Compare two strings, ignoring case
+///
+/// Expects two values on the stack: compared strings. Returns one of the
+/// following numbers: 0, -1 or 1.
+///
+/// Does no error handling: never call it with non-string or with some arguments
+/// omitted.
+static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ const char *s1 = luaL_checklstring(lstate, 1, NULL);
+ const char *s2 = luaL_checklstring(lstate, 2, NULL);
+ const int ret = STRICMP(s1, s2);
+ lua_pop(lstate, 2);
+ lua_pushnumber(lstate, (lua_Number)((ret > 0) - (ret < 0)));
+ return 1;
+}
+
+/// Evaluate lua string
+///
+/// Expects two values on the stack: string to evaluate, pointer to the
+/// location where result is saved. Always returns nothing (from the lua point
+/// of view).
+static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ const String *const str = (const String *)lua_touserdata(lstate, 1);
+ typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 2);
+ lua_pop(lstate, 2);
+
+ if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) {
+ nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s"));
+ return 0;
+ }
+ if (lua_pcall(lstate, 0, 1, 0)) {
+ nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s"));
+ return 0;
+ }
+ if (!nlua_pop_typval(lstate, ret_tv)) {
+ return 0;
+ }
+ return 0;
+}
+
+/// Evaluate lua string for each line in range
+///
+/// Expects two values on the stack: string to evaluate and pointer to integer
+/// array with line range. Always returns nothing (from the lua point of view).
+static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ const String *const str = (const String *)lua_touserdata(lstate, 1);
+ const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 2);
+ lua_pop(lstate, 2);
+
+#define DOSTART "return function(line, linenr) "
+#define DOEND " end"
+ const size_t lcmd_len = (str->size
+ + (sizeof(DOSTART) - 1)
+ + (sizeof(DOEND) - 1));
+ char *lcmd;
+ if (lcmd_len < IOSIZE) {
+ lcmd = (char *)IObuff;
+ } else {
+ lcmd = xmalloc(lcmd_len + 1);
+ }
+ memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1);
+ memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size);
+ memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, DOEND, sizeof(DOEND) - 1);
+#undef DOSTART
+#undef DOEND
+
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
+ nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s"));
+ if (lcmd_len >= IOSIZE) {
+ xfree(lcmd);
+ }
+ return 0;
+ }
+ if (lcmd_len >= IOSIZE) {
+ xfree(lcmd);
+ }
+ if (lua_pcall(lstate, 0, 1, 0)) {
+ nlua_error(lstate, _("E5110: Error while creating lua function: %.*s"));
+ return 0;
+ }
+ for (linenr_T l = range[0]; l <= range[1]; l++) {
+ if (l > curbuf->b_ml.ml_line_count) {
+ break;
+ }
+ lua_pushvalue(lstate, -1);
+ lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false));
+ lua_pushnumber(lstate, (lua_Number)l);
+ if (lua_pcall(lstate, 2, 1, 0)) {
+ nlua_error(lstate, _("E5111: Error while calling lua function: %.*s"));
+ break;
+ }
+ if (lua_isstring(lstate, -1)) {
+ size_t new_line_len;
+ const char *const new_line = lua_tolstring(lstate, -1, &new_line_len);
+ char *const new_line_transformed = xmemdupz(new_line, new_line_len);
+ for (size_t i = 0; i < new_line_len; i++) {
+ if (new_line_transformed[i] == NUL) {
+ new_line_transformed[i] = '\n';
+ }
+ }
+ ml_replace(l, (char_u *)new_line_transformed, false);
+ changed_bytes(l, 0);
+ }
+ lua_pop(lstate, 1);
+ }
+ lua_pop(lstate, 1);
+ check_cursor();
+ update_screen(NOT_VALID);
+ return 0;
+}
+
+/// Evaluate lua file
+///
+/// Expects one value on the stack: file to evaluate. Always returns nothing
+/// (from the lua point of view).
+static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ const char *const filename = (const char *)lua_touserdata(lstate, 1);
+ lua_pop(lstate, 1);
+
+ if (luaL_loadfile(lstate, filename)) {
+ nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s"));
+ return 0;
+ }
+ if (lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s"));
+ return 0;
+ }
+ return 0;
+}
+
+/// Initialize lua interpreter state
+///
+/// Called by lua interpreter itself to initialize state.
+static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ // stricmp
+ lua_pushcfunction(lstate, &nlua_stricmp);
+ lua_setglobal(lstate, "stricmp");
+
+ // print
+ lua_pushcfunction(lstate, &nlua_print);
+ lua_setglobal(lstate, "print");
+
+ // debug.debug
+ lua_getglobal(lstate, "debug");
+ lua_pushcfunction(lstate, &nlua_debug);
+ lua_setfield(lstate, -2, "debug");
+ lua_pop(lstate, 1);
+
+ // vim
+ if (luaL_dostring(lstate, (char *)&vim_module[0])) {
+ nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
+ return 1;
+ }
+ // vim.api
+ nlua_add_api_functions(lstate);
+ // vim.types, vim.type_idx, vim.val_idx
+ nlua_init_types(lstate);
+ lua_setglobal(lstate, "vim");
+ return 0;
+}
+
+/// Initialize lua interpreter
+///
+/// Crashes NeoVim if initialization fails. Should be called once per lua
+/// interpreter instance.
+static lua_State *init_lua(void)
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ lua_State *lstate = luaL_newstate();
+ if (lstate == NULL) {
+ EMSG(_("E970: Failed to initialize lua interpreter"));
+ preserve_exit();
+ }
+ luaL_openlibs(lstate);
+ NLUA_CALL_C_FUNCTION_0(lstate, nlua_state_init, 0);
+ return lstate;
+}
+
+static lua_State *global_lstate = NULL;
+
+/// Execute lua string
+///
+/// @param[in] str String to execute.
+/// @param[out] ret_tv Location where result will be saved.
+///
+/// @return Result of the execution.
+void executor_exec_lua(const String str, typval_T *const ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (global_lstate == NULL) {
+ global_lstate = init_lua();
+ }
+
+ NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0,
+ (void *)&str, ret_tv);
+}
+
+/// Evaluate lua string
+///
+/// Used for luaeval(). Expects three values on the stack:
+///
+/// 1. String to evaluate.
+/// 2. _A value.
+/// 3. Pointer to location where result is saved.
+///
+/// @param[in,out] lstate Lua interpreter state.
+static int nlua_eval_lua_string(lua_State *const lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const String *const str = (const String *)lua_touserdata(lstate, 1);
+ typval_T *const arg = (typval_T *)lua_touserdata(lstate, 2);
+ typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 3);
+ lua_pop(lstate, 3);
+
+ garray_T str_ga;
+ ga_init(&str_ga, 1, 80);
+#define EVALHEADER "local _A=select(1,...) return ("
+ const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str->size + 1;
+ char *lcmd;
+ if (lcmd_len < IOSIZE) {
+ lcmd = (char *)IObuff;
+ } else {
+ lcmd = xmalloc(lcmd_len);
+ }
+ memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1);
+ memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size);
+ lcmd[lcmd_len - 1] = ')';
+#undef EVALHEADER
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
+ nlua_error(lstate,
+ _("E5107: Error while creating lua chunk for luaeval(): %.*s"));
+ if (lcmd != (char *)IObuff) {
+ xfree(lcmd);
+ }
+ return 0;
+ }
+ if (lcmd != (char *)IObuff) {
+ xfree(lcmd);
+ }
+
+ if (arg == NULL || arg->v_type == VAR_UNKNOWN) {
+ lua_pushnil(lstate);
+ } else {
+ nlua_push_typval(lstate, arg);
+ }
+ if (lua_pcall(lstate, 1, 1, 0)) {
+ nlua_error(lstate,
+ _("E5108: Error while calling lua chunk for luaeval(): %.*s"));
+ return 0;
+ }
+ if (!nlua_pop_typval(lstate, ret_tv)) {
+ return 0;
+ }
+
+ return 0;
+}
+
+/// Print as a Vim message
+///
+/// @param lstate Lua interpreter state.
+static int nlua_print(lua_State *const lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+#define PRINT_ERROR(msg) \
+ do { \
+ errmsg = msg; \
+ errmsg_len = sizeof(msg) - 1; \
+ goto nlua_print_error; \
+ } while (0)
+ const int nargs = lua_gettop(lstate);
+ lua_getglobal(lstate, "tostring");
+ const char *errmsg = NULL;
+ size_t errmsg_len = 0;
+ garray_T msg_ga;
+ ga_init(&msg_ga, 1, 80);
+ int curargidx = 1;
+ for (; curargidx <= nargs; curargidx++) {
+ lua_pushvalue(lstate, -1); // tostring
+ lua_pushvalue(lstate, curargidx); // arg
+ if (lua_pcall(lstate, 1, 1, 0)) {
+ errmsg = lua_tolstring(lstate, -1, &errmsg_len);
+ goto nlua_print_error;
+ }
+ size_t len;
+ const char *const s = lua_tolstring(lstate, -1, &len);
+ if (s == NULL) {
+ PRINT_ERROR(
+ "<Unknown error: lua_tolstring returned NULL for tostring result>");
+ }
+ ga_concat_len(&msg_ga, s, len);
+ if (curargidx < nargs) {
+ ga_append(&msg_ga, ' ');
+ }
+ lua_pop(lstate, 1);
+ }
+#undef PRINT_ERROR
+ lua_pop(lstate, nargs + 1);
+ ga_append(&msg_ga, NUL);
+ {
+ const size_t len = (size_t)msg_ga.ga_len - 1;
+ char *const str = (char *)msg_ga.ga_data;
+
+ for (size_t i = 0; i < len;) {
+ const size_t start = i;
+ while (i < len) {
+ switch (str[i]) {
+ case NUL: {
+ str[i] = NL;
+ i++;
+ continue;
+ }
+ case NL: {
+ str[i] = NUL;
+ i++;
+ break;
+ }
+ default: {
+ i++;
+ continue;
+ }
+ }
+ break;
+ }
+ msg((char_u *)str + start);
+ }
+ if (str[len - 1] == NUL) { // Last was newline
+ msg((char_u *)"");
+ }
+ }
+ ga_clear(&msg_ga);
+ return 0;
+nlua_print_error:
+ emsgf(_("E5114: Error while converting print argument #%i: %.*s"),
+ curargidx, errmsg_len, errmsg);
+ ga_clear(&msg_ga);
+ lua_pop(lstate, lua_gettop(lstate));
+ return 0;
+}
+
+/// debug.debug implementation: interaction with user while debugging
+///
+/// @param lstate Lua interpreter state.
+int nlua_debug(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const typval_T input_args[] = {
+ {
+ .v_lock = VAR_FIXED,
+ .v_type = VAR_STRING,
+ .vval.v_string = (char_u *)"lua_debug> ",
+ },
+ {
+ .v_type = VAR_UNKNOWN,
+ },
+ };
+ for (;;) {
+ lua_settop(lstate, 0);
+ typval_T input;
+ get_user_input(input_args, &input, false);
+ msg_putchar('\n'); // Avoid outputting on input line.
+ if (input.v_type != VAR_STRING
+ || input.vval.v_string == NULL
+ || *input.vval.v_string == NUL
+ || STRCMP(input.vval.v_string, "cont") == 0) {
+ tv_clear(&input);
+ return 0;
+ }
+ if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string,
+ STRLEN(input.vval.v_string), "=(debug command)")) {
+ nlua_error(lstate, _("E5115: Error while loading debug string: %.*s"));
+ }
+ tv_clear(&input);
+ if (lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5116: Error while calling debug string: %.*s"));
+ }
+ }
+ return 0;
+}
+
+/// Evaluate lua string
+///
+/// Used for luaeval().
+///
+/// @param[in] str String to execute.
+/// @param[in] arg Second argument to `luaeval()`.
+/// @param[out] ret_tv Location where result will be saved.
+///
+/// @return Result of the execution.
+void executor_eval_lua(const String str, typval_T *const arg,
+ typval_T *const ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (global_lstate == NULL) {
+ global_lstate = init_lua();
+ }
+
+ NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0,
+ (void *)&str, arg, ret_tv);
+}
+
+/// Run lua string
+///
+/// Used for :lua.
+///
+/// @param eap VimL command being run.
+void ex_lua(exarg_T *const eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t len;
+ char *const code = script_get(eap, &len);
+ if (eap->skip) {
+ xfree(code);
+ return;
+ }
+ typval_T tv = { .v_type = VAR_UNKNOWN };
+ executor_exec_lua((String) { .data = code, .size = len }, &tv);
+ tv_clear(&tv);
+ xfree(code);
+}
+
+/// Run lua string for each line in range
+///
+/// Used for :luado.
+///
+/// @param eap VimL command being run.
+void ex_luado(exarg_T *const eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (global_lstate == NULL) {
+ global_lstate = init_lua();
+ }
+ if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) {
+ EMSG(_("cannot save undo information"));
+ return;
+ }
+ const String cmd = {
+ .size = STRLEN(eap->arg),
+ .data = (char *)eap->arg,
+ };
+ const linenr_T range[] = { eap->line1, eap->line2 };
+ NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_luado_string, 0,
+ (void *)&cmd, (void *)range);
+}
+
+/// Run lua file
+///
+/// Used for :luafile.
+///
+/// @param eap VimL command being run.
+void ex_luafile(exarg_T *const eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (global_lstate == NULL) {
+ global_lstate = init_lua();
+ }
+ NLUA_CALL_C_FUNCTION_1(global_lstate, nlua_exec_lua_file, 0,
+ (void *)eap->arg);
+}
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
new file mode 100644
index 0000000000..0cbf290f64
--- /dev/null
+++ b/src/nvim/lua/executor.h
@@ -0,0 +1,25 @@
+#ifndef NVIM_LUA_EXECUTOR_H
+#define NVIM_LUA_EXECUTOR_H
+
+#include <lua.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/func_attr.h"
+#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
+
+// Generated by msgpack-gen.lua
+void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
+
+#define set_api_error(s, err) \
+ do { \
+ Error *err_ = (err); \
+ err_->type = kErrorTypeException; \
+ err_->set = true; \
+ memcpy(&err_->msg[0], s, sizeof(s)); \
+ } while (0)
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/executor.h.generated.h"
+#endif
+#endif // NVIM_LUA_EXECUTOR_H
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
new file mode 100644
index 0000000000..8d1c5bdf4f
--- /dev/null
+++ b/src/nvim/lua/vim.lua
@@ -0,0 +1,2 @@
+-- TODO(ZyX-I): Create compatibility layer.
+return {}
diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c
index 91ef5524ea..444c6cc256 100644
--- a/src/nvim/msgpack_rpc/helpers.c
+++ b/src/nvim/msgpack_rpc/helpers.c
@@ -24,12 +24,12 @@ static msgpack_zone zone;
static msgpack_sbuffer sbuffer;
#define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \
- bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \
- Integer *const arg) \
- FUNC_ATTR_NONNULL_ALL \
+ static bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \
+ Integer *const arg) \
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
{ \
if (obj->type != MSGPACK_OBJECT_EXT \
- || obj->via.ext.type != kObjectType##t) { \
+ || obj->via.ext.type + EXT_OBJECT_TYPE_SHIFT != kObjectType##t) { \
return false; \
} \
\
@@ -48,13 +48,14 @@ static msgpack_sbuffer sbuffer;
return true; \
} \
\
- void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \
+ static void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \
FUNC_ATTR_NONNULL_ARG(2) \
{ \
msgpack_packer pac; \
msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \
msgpack_pack_int64(&pac, (handle_T)o); \
- msgpack_pack_ext(res, sbuffer.size, kObjectType##t); \
+ msgpack_pack_ext(res, sbuffer.size, \
+ kObjectType##t - EXT_OBJECT_TYPE_SHIFT); \
msgpack_pack_ext_body(res, sbuffer.data, sbuffer.size); \
msgpack_sbuffer_clear(&sbuffer); \
}
@@ -126,7 +127,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
{
STATIC_ASSERT(sizeof(Float) == sizeof(cur.mobj->via.f64),
"Msgpack floating-point size does not match API integer");
- *cur.aobj = FLOATING_OBJ(cur.mobj->via.f64);
+ *cur.aobj = FLOAT_OBJ(cur.mobj->via.f64);
break;
}
#define STR_CASE(type, attr, obj, dest, conv) \
@@ -225,7 +226,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
break;
}
case MSGPACK_OBJECT_EXT: {
- switch (cur.mobj->via.ext.type) {
+ switch ((ObjectType)(cur.mobj->via.ext.type + EXT_OBJECT_TYPE_SHIFT)) {
case kObjectTypeBuffer: {
cur.aobj->type = kObjectTypeBuffer;
ret = msgpack_rpc_to_buffer(cur.mobj, &cur.aobj->data.integer);
@@ -241,6 +242,15 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
ret = msgpack_rpc_to_tabpage(cur.mobj, &cur.aobj->data.integer);
break;
}
+ case kObjectTypeNil:
+ case kObjectTypeBoolean:
+ case kObjectTypeInteger:
+ case kObjectTypeFloat:
+ case kObjectTypeString:
+ case kObjectTypeArray:
+ case kObjectTypeDictionary: {
+ break;
+ }
}
break;
}
@@ -364,6 +374,9 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res)
kv_push(stack, ((APIToMPObjectStackItem) { &result, false, 0 }));
while (kv_size(stack)) {
APIToMPObjectStackItem cur = kv_last(stack);
+ STATIC_ASSERT(kObjectTypeWindow == kObjectTypeBuffer + 1
+ && kObjectTypeTabpage == kObjectTypeWindow + 1,
+ "Buffer, window and tabpage enum items are in order");
switch (cur.aobj->type) {
case kObjectTypeNil: {
msgpack_pack_nil(res);
diff --git a/src/nvim/msgpack_rpc/helpers.h b/src/nvim/msgpack_rpc/helpers.h
index 7d9f114140..0e4cd1be6d 100644
--- a/src/nvim/msgpack_rpc/helpers.h
+++ b/src/nvim/msgpack_rpc/helpers.h
@@ -9,6 +9,13 @@
#include "nvim/event/wstream.h"
#include "nvim/api/private/defs.h"
+/// Value by which objects represented as EXT type are shifted
+///
+/// Subtracted when packing, added when unpacking. Used to allow moving
+/// buffer/window/tabpage block inside ObjectType enum. This block yet cannot be
+/// split or reordered.
+#define EXT_OBJECT_TYPE_SHIFT kObjectTypeBuffer
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/helpers.h.generated.h"
#endif