diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-30 20:35:25 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-30 20:35:25 +0000 |
commit | 1b7b916b7631ddf73c38e3a0070d64e4636cb2f3 (patch) | |
tree | cd08258054db80bb9a11b1061bb091c70b76926a /src/nvim/lua/executor.c | |
parent | eaa89c11d0f8aefbb512de769c6c82f61a8baca3 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-aucmd_textputpost.tar.gz rneovim-aucmd_textputpost.tar.bz2 rneovim-aucmd_textputpost.zip |
Merge remote-tracking branch 'upstream/master' into aucmd_textputpostaucmd_textputpost
Diffstat (limited to 'src/nvim/lua/executor.c')
-rw-r--r-- | src/nvim/lua/executor.c | 285 |
1 files changed, 119 insertions, 166 deletions
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 1415ceeaed..06d16efb05 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1,12 +1,11 @@ -// 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 <inttypes.h> #include <lauxlib.h> #include <lua.h> #include <lualib.h> #include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include <tree_sitter/api.h> #include <uv.h> @@ -16,9 +15,9 @@ #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii.h" -#include "nvim/buffer_defs.h" +#include "nvim/ascii_defs.h" #include "nvim/change.h" +#include "nvim/cmdexpand_defs.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" @@ -42,25 +41,24 @@ #include "nvim/lua/executor.h" #include "nvim/lua/stdlib.h" #include "nvim/lua/treesitter.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/main.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/option_defs.h" +#include "nvim/option_vars.h" #include "nvim/os/fileio.h" #include "nvim/os/os.h" #include "nvim/path.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" #include "nvim/profile.h" #include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/usercmd.h" -#include "nvim/version.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" static int in_fast_callback = 0; @@ -109,11 +107,16 @@ typedef enum luv_err_type { kThreadCallback, } luv_err_t; +lua_State *get_global_lstate(void) +{ + return global_lstate; +} + /// 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) +/// @param[in] msg Message base, must contain one `%*s`. +void nlua_error(lua_State *const lstate, const char *const msg) FUNC_ATTR_NONNULL_ALL { size_t len; @@ -135,8 +138,8 @@ static void nlua_error(lua_State *const lstate, const char *const msg) } if (in_script) { - os_errmsg(str); - os_errmsg("\n"); + fprintf(stderr, msg, (int)len, str); + fprintf(stderr, "\n"); } else { msg_ext_set_kind("lua_error"); semsg_multiline(msg, (int)len, str); @@ -150,7 +153,7 @@ static void nlua_error(lua_State *const lstate, const char *const msg) /// @param lstate Lua interpreter state /// @param[in] nargs Number of arguments expected by the function being called. /// @param[in] nresults Number of results the function returns. -static int nlua_pcall(lua_State *lstate, int nargs, int nresults) +int nlua_pcall(lua_State *lstate, int nargs, int nresults) { lua_getglobal(lstate, "debug"); lua_getfield(lstate, -1, "traceback"); @@ -165,17 +168,6 @@ static int nlua_pcall(lua_State *lstate, int nargs, int nresults) return status; } -/// Gets the version of the current Nvim build. -/// -/// @param lstate Lua interpreter state. -static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - Dictionary version = version_dict(); - nlua_push_Dictionary(lstate, version, true); - api_free_dictionary(version); - return 1; -} - static void nlua_luv_error_event(void **argv) { char *error = (char *)argv[0]; @@ -211,9 +203,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags if (status) { if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { // consider out of memory errors unrecoverable, just like xmalloc() - os_errmsg(e_outofmem); - os_errmsg("\n"); - preserve_exit(); + preserve_exit(e_outofmem); } const char *error = lua_tostring(lstate, -1); @@ -349,7 +339,7 @@ static int nlua_init_argv(lua_State *const L, char **argv, int argc, int lua_arg lua_pushstring(L, argv[lua_arg0 - 1]); lua_rawseti(L, -2, 0); // _G.arg[0] = "foo.lua" - for (; lua_arg0 >= 0 && i + lua_arg0 < argc; i++) { + for (; i + lua_arg0 < argc; i++) { lua_pushstring(L, argv[i + lua_arg0]); lua_rawseti(L, -2, i + 1); // _G.arg[i+1] = "--foo" } @@ -381,6 +371,12 @@ static int nlua_schedule(lua_State *const lstate) return lua_error(lstate); } + // If main_loop is closing don't schedule tasks to run in the future, + // otherwise any refs allocated here will not be cleaned up. + if (main_loop.closing) { + return 0; + } + LuaRef cb = nlua_ref_global(lstate, 1); multiqueue_put(main_loop.events, nlua_schedule_event, @@ -390,7 +386,8 @@ static int nlua_schedule(lua_State *const lstate) // Dummy timer callback. Used by f_wait(). static void dummy_timer_due_cb(TimeWatcher *tw, void *data) -{} +{ +} // Dummy timer close callback. Used by f_wait(). static void dummy_timer_close_cb(TimeWatcher *tw, void *data) @@ -414,6 +411,10 @@ static bool nlua_wait_condition(lua_State *lstate, int *status, bool *callback_r static int nlua_wait(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { + if (in_fast_callback) { + return luaL_error(lstate, e_luv_api_disabled, "vim.wait"); + } + intptr_t timeout = luaL_checkinteger(lstate, 1); if (timeout < 0) { return luaL_error(lstate, "timeout must be >= 0"); @@ -452,8 +453,7 @@ static int nlua_wait(lua_State *lstate) fast_only = lua_toboolean(lstate, 4); } - MultiQueue *loop_events = fast_only || in_fast_callback > 0 - ? main_loop.fast_events : main_loop.events; + MultiQueue *loop_events = fast_only ? main_loop.fast_events : main_loop.events; TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); @@ -469,12 +469,16 @@ static int nlua_wait(lua_State *lstate) int pcall_status = 0; bool callback_result = false; + // Flush screen updates before blocking. + ui_flush(); + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, loop_events, (int)timeout, got_int || (is_function ? nlua_wait_condition(lstate, &pcall_status, - &callback_result) : false)); + &callback_result) + : false)); // Stop dummy timer time_watcher_stop(tw); @@ -572,7 +576,7 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread, bool is_stan lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict"); lua_setfield(lstate, -2, "_empty_dict_mt"); - // vim.loop + // vim.uv if (is_standalone) { // do nothing, use libluv like in a standalone interpreter } else if (is_thread) { @@ -585,9 +589,12 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread, bool is_stan } luaopen_luv(lstate); lua_pushvalue(lstate, -1); - lua_setfield(lstate, -3, "loop"); + lua_setfield(lstate, -3, "uv"); + + lua_pushvalue(lstate, -1); + lua_setfield(lstate, -3, "loop"); // deprecated - // package.loaded.luv = vim.loop + // package.loaded.luv = vim.uv // otherwise luv will be reinitialized when require'luv' lua_getglobal(lstate, "package"); lua_getfield(lstate, -1, "loaded"); @@ -622,7 +629,7 @@ static bool nlua_init_packages(lua_State *lstate, bool is_standalone) lua_getfield(lstate, -1, "preload"); // [package, preload] for (size_t i = 0; i < ARRAY_SIZE(builtin_modules); i++) { ModuleDef def = builtin_modules[i]; - lua_pushinteger(lstate, (long)i); // [package, preload, i] + lua_pushinteger(lstate, (lua_Integer)i); // [package, preload, i] lua_pushcclosure(lstate, nlua_module_preloader, 1); // [package, preload, cclosure] lua_setfield(lstate, -2, def.name); // [package, preload] @@ -636,7 +643,7 @@ static bool nlua_init_packages(lua_State *lstate, bool is_standalone) lua_getglobal(lstate, "require"); lua_pushstring(lstate, "vim._init_packages"); if (nlua_pcall(lstate, 1, 0)) { - os_errmsg((char *)lua_tostring(lstate, -1)); + os_errmsg(lua_tostring(lstate, -1)); os_errmsg("\n"); return false; } @@ -741,10 +748,6 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // vim.types, vim.type_idx, vim.val_idx nlua_init_types(lstate); - // neovim version - lua_pushcfunction(lstate, &nlua_nvim_version); - lua_setfield(lstate, -2, "version"); - // schedule lua_pushcfunction(lstate, &nlua_schedule); lua_setfield(lstate, -2, "schedule"); @@ -852,7 +855,7 @@ void nlua_run_script(char **argv, int argc, int lua_arg0) exit(lua_ok ? 0 : 1); } -lua_State *nlua_init_state(bool thread) +static lua_State *nlua_init_state(bool thread) { // If it is called from the main thread, it will attempt to rebuild the cache. const uv_thread_t self = uv_thread_self(); @@ -926,12 +929,13 @@ static void nlua_common_free_all_mem(lua_State *lstate) if (nlua_track_refs) { // in case there are leaked luarefs, leak the associated memory // to get LeakSanitizer stacktraces on exit - pmap_destroy(handle_T)(&ref_state->ref_markers); + map_destroy(int, &ref_state->ref_markers); } #endif lua_close(lstate); } + static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -960,10 +964,13 @@ static void nlua_print_event(void **argv) } break; } - msg(str + start); + msg(str + start, 0); + if (msg_silent == 0) { + msg_didout = true; // Make blank lines work properly + } } if (len && str[len - 1] == NUL) { // Last was newline - msg(""); + msg("", 0); } xfree(str); } @@ -1110,7 +1117,7 @@ static int nlua_debug(lua_State *lstate) .v_type = VAR_UNKNOWN, }, }; - for (;;) { + while (true) { lua_settop(lstate, 0); typval_T input; get_user_input(input_args, &input, false, false); @@ -1122,7 +1129,7 @@ static int nlua_debug(lua_State *lstate) tv_clear(&input); return 0; } - if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string, + if (luaL_loadbuffer(lstate, input.vval.v_string, strlen(input.vval.v_string), "=(debug command)")) { nlua_error(lstate, _("E5115: Error while loading debug string: %.*s")); } else if (nlua_pcall(lstate, 0, 0)) { @@ -1145,7 +1152,7 @@ static bool viml_func_is_fast(const char *name) if (fdef) { return fdef->fast; } - // Not a vimL function + // Not a Vimscript function return false; } @@ -1155,7 +1162,7 @@ int nlua_call(lua_State *lstate) size_t name_len; const char *name = luaL_checklstring(lstate, 1, &name_len); if (!nlua_is_deferred_safe() && !viml_func_is_fast(name)) { - return luaL_error(lstate, e_luv_api_disabled, "vimL function"); + return luaL_error(lstate, e_luv_api_disabled, "Vimscript function"); } int nargs = lua_gettop(lstate) - 1; @@ -1174,28 +1181,29 @@ int nlua_call(lua_State *lstate) } } - TRY_WRAP({ - // TODO(bfredl): this should be simplified in error handling refactor - force_abort = false; - suppress_errthrow = false; - did_throw = false; - did_emsg = false; + // TODO(bfredl): this should be simplified in error handling refactor + force_abort = false; + suppress_errthrow = false; + did_throw = false; + did_emsg = false; - try_start(); - typval_T rettv; - funcexe_T funcexe = FUNCEXE_INIT; - funcexe.fe_firstline = curwin->w_cursor.lnum; - funcexe.fe_lastline = curwin->w_cursor.lnum; - funcexe.fe_evaluate = true; + typval_T rettv; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = true; + + TRY_WRAP(&err, { // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (TRY_WRAP) to capture abort-causing non-exception errors. - (void)call_func((char *)name, (int)name_len, &rettv, nargs, vim_args, &funcexe); - if (!try_end(&err)) { - nlua_push_typval(lstate, &rettv, false); - } - tv_clear(&rettv); + (void)call_func(name, (int)name_len, &rettv, nargs, vim_args, &funcexe); }); + if (!ERROR_SET(&err)) { + nlua_push_typval(lstate, &rettv, false); + } + tv_clear(&rettv); + free_vim_args: while (i > 0) { tv_clear(&vim_args[--i]); @@ -1300,13 +1308,16 @@ LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index) #ifdef NLUA_TRACK_REFS if (nlua_track_refs) { // dummy allocation to make LeakSanitizer track our luarefs - pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3)); + pmap_put(int)(&ref_state->ref_markers, ref, xmalloc(3)); } #endif } return ref; } +// TODO(lewis6991): Currently cannot be run in __gc metamethods as they are +// invoked in lua_close() which can be invoked after the ref_markers map is +// destroyed in nlua_common_free_all_mem. LuaRef nlua_ref_global(lua_State *lstate, int index) { return nlua_ref(lstate, nlua_global_refs, index); @@ -1320,7 +1331,7 @@ void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref) #ifdef NLUA_TRACK_REFS // NB: don't remove entry from map to track double-unref if (nlua_track_refs) { - xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref)); + xfree(pmap_get(int)(&ref_state->ref_markers, ref)); } #endif luaL_unref(lstate, LUA_REGISTRYINDEX, ref); @@ -1493,7 +1504,7 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) /// Call a LuaCallable given some typvals /// -/// Used to call any lua callable passed from Lua into VimL +/// Used to call any Lua callable passed from Lua into Vimscript. /// /// @param[in] lstate Lua State /// @param[in] lua_cb Lua Callable @@ -1628,24 +1639,25 @@ bool nlua_is_deferred_safe(void) /// /// Used for :lua. /// -/// @param eap VimL command being run. +/// @param eap Vimscript command being run. void ex_lua(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { size_t len; char *code = script_get(eap, &len); - if (eap->skip) { + if (eap->skip || code == NULL) { xfree(code); return; } - // When =expr is used transform it to print(vim.inspect(expr)) - if (code[0] == '=') { - len += sizeof("vim.pretty_print()") - sizeof("="); + // When =expr is used transform it to vim.print(expr) + if (eap->cmdidx == CMD_equal || code[0] == '=') { + size_t off = (eap->cmdidx == CMD_equal) ? 0 : 1; + len += sizeof("vim.print()") - 1 - off; // code_buf needs to be 1 char larger then len for null byte in the end. // lua nlua_typval_exec doesn't expect null terminated string so len // needs to end before null byte. char *code_buf = xmallocz(len); - vim_snprintf(code_buf, len + 1, "vim.pretty_print(%s)", code + 1); + vim_snprintf(code_buf, len + 1, "vim.print(%s)", code + off); xfree(code); code = code_buf; } @@ -1659,7 +1671,7 @@ void ex_lua(exarg_T *const eap) /// /// Used for :luado. /// -/// @param eap VimL command being run. +/// @param eap Vimscript command being run. void ex_luado(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { @@ -1667,7 +1679,7 @@ void ex_luado(exarg_T *const eap) emsg(_("cannot save undo information")); return; } - const char *const cmd = (const char *)eap->arg; + const char *const cmd = eap->arg; const size_t cmd_len = strlen(cmd); lua_State *const lstate = global_lstate; @@ -1708,7 +1720,9 @@ void ex_luado(exarg_T *const eap) break; } lua_pushvalue(lstate, -1); - const char *old_line = (const char *)ml_get_buf(curbuf, l, false); + const char *const old_line = ml_get_buf(curbuf, l); + // Get length of old_line here as calling Lua code may free it. + const size_t old_line_len = strlen(old_line); lua_pushstring(lstate, old_line); lua_pushnumber(lstate, (lua_Number)l); if (nlua_pcall(lstate, 2, 1)) { @@ -1716,8 +1730,6 @@ void ex_luado(exarg_T *const eap) break; } if (lua_isstring(lstate, -1)) { - size_t old_line_len = strlen(old_line); - 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); @@ -1740,11 +1752,11 @@ void ex_luado(exarg_T *const eap) /// /// Used for :luafile. /// -/// @param eap VimL command being run. +/// @param eap Vimscript command being run. void ex_luafile(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { - nlua_exec_file((const char *)eap->arg); + nlua_exec_file(eap->arg); } /// Executes Lua code from a file or "-" (stdin). @@ -1768,13 +1780,12 @@ bool nlua_exec_file(const char *path) StringBuilder sb = KV_INITIAL_VALUE; kv_resize(sb, 64); - ptrdiff_t read_size = -1; // Read all input from stdin, unless interrupted (ctrl-c). while (true) { if (got_int) { // User canceled. return false; } - read_size = file_read(stdin_dup, IObuff, 64); + ptrdiff_t read_size = file_read(stdin_dup, IObuff, 64); if (read_size < 0) { // Error. return false; } @@ -1876,7 +1887,7 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results) luaL_checktype(lstate, -1, LUA_TFUNCTION); // [ vim, vim._expand_pat, buf ] - lua_pushlstring(lstate, (const char *)pat, strlen(pat)); + lua_pushlstring(lstate, pat, strlen(pat)); if (nlua_pcall(lstate, 1, 2) != 0) { nlua_error(lstate, @@ -1995,7 +2006,7 @@ char *nlua_register_table_as_callable(const typval_T *const arg) void nlua_execute_on_key(int c) { char buf[NUMBUFLEN]; - size_t buf_len = special_to_buf(c, mod_mask, false, (char_u *)buf); + size_t buf_len = special_to_buf(c, mod_mask, false, buf); lua_State *const lstate = global_lstate; @@ -2041,9 +2052,9 @@ void nlua_set_sctx(sctx_T *current) lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug)); // Files where internal wrappers are defined so we can ignore them - // like vim.o/opt etc are defined in _meta.lua + // like vim.o/opt etc are defined in _options.lua char *ignorelist[] = { - "vim/_meta.lua", + "vim/_options.lua", "vim/keymap.lua", }; int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]); @@ -2073,10 +2084,15 @@ void nlua_set_sctx(sctx_T *current) break; } char *source_path = fix_fname(info->source + 1); - get_current_script_id(&source_path, current); - xfree(source_path); - current->sc_lnum = info->currentline; + int sid = find_script_by_name(source_path); + if (sid > 0) { + xfree(source_path); + } else { + new_script_item(source_path, &sid); + } + current->sc_sid = sid; current->sc_seq = -1; + current->sc_lnum = info->currentline; cleanup: xfree(info); @@ -2103,7 +2119,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) lua_setfield(lstate, -2, "line2"); lua_newtable(lstate); // f-args table - lua_pushstring(lstate, (const char *)eap->arg); + lua_pushstring(lstate, eap->arg); lua_pushvalue(lstate, -1); // Reference for potential use on f-args lua_setfield(lstate, -4, "args"); @@ -2286,79 +2302,16 @@ plain: return str.items; } -char *nlua_read_secure(const char *path) -{ - lua_State *const lstate = global_lstate; - const int top = lua_gettop(lstate); - - lua_getglobal(lstate, "vim"); - lua_getfield(lstate, -1, "secure"); - lua_getfield(lstate, -1, "read"); - lua_pushstring(lstate, path); - if (nlua_pcall(lstate, 1, 1)) { - nlua_error(lstate, _("Error executing vim.secure.read: %.*s")); - lua_settop(lstate, top); - return NULL; - } - - size_t len = 0; - const char *contents = lua_tolstring(lstate, -1, &len); - char *buf = NULL; - if (contents != NULL) { - // Add one to include trailing null byte - buf = xcalloc(len + 1, sizeof(char)); - memcpy(buf, contents, len + 1); - } - - lua_settop(lstate, top); - return buf; -} - -bool nlua_trust(const char *action, const char *path) +/// Execute the vim._defaults module to set up default mappings and autocommands +void nlua_init_defaults(void) { - lua_State *const lstate = global_lstate; - const int top = lua_gettop(lstate); + lua_State *const L = global_lstate; + assert(L); - lua_getglobal(lstate, "vim"); - lua_getfield(lstate, -1, "secure"); - lua_getfield(lstate, -1, "trust"); - - lua_newtable(lstate); - lua_pushstring(lstate, "action"); - lua_pushstring(lstate, action); - lua_settable(lstate, -3); - if (path == NULL) { - lua_pushstring(lstate, "bufnr"); - lua_pushnumber(lstate, 0); - lua_settable(lstate, -3); - } else { - lua_pushstring(lstate, "path"); - lua_pushstring(lstate, path); - lua_settable(lstate, -3); - } - - if (nlua_pcall(lstate, 1, 2)) { - nlua_error(lstate, _("Error executing vim.secure.trust: %.*s")); - lua_settop(lstate, top); - return false; - } - - bool success = lua_toboolean(lstate, -2); - const char *msg = lua_tostring(lstate, -1); - if (msg != NULL) { - if (success) { - if (strcmp(action, "allow") == 0) { - smsg("Allowed \"%s\" in trust database.", msg); - } else if (strcmp(action, "deny") == 0) { - smsg("Denied \"%s\" in trust database.", msg); - } else if (strcmp(action, "remove") == 0) { - smsg("Removed \"%s\" from trust database.", msg); - } - } else { - semsg(e_trustfile, msg); - } + lua_getglobal(L, "require"); + lua_pushstring(L, "vim._defaults"); + if (nlua_pcall(L, 1, 0)) { + os_errmsg(lua_tostring(L, -1)); + os_errmsg("\n"); } - - lua_settop(lstate, top); - return success; } |