Emacs <-> Lua via Emacs's dynamic modules

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Emacs <-> Lua via Emacs's dynamic modules

Eduardo Ochs
Hi people,

are you aware of any attempts to link Emacs and Lua using this?

  http://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html
  http://www.gnu.org/software/emacs/manual/html_node/elisp/Writing-Dynamic-Modules.html

Even a barebones prototype that only allows sending a string a Lua and
then have Lua send back a string to Emacs would be useful to me...

Here is an example of an Emacs library that is partly implemented in C:

  https://github.com/akermu/emacs-libvterm
  https://emacsconf.org/2020/talks/30/

Cheers and TIA,
  Eduardo Ochs
  http://angg.twu.net/luaforth.html
  http://angg.twu.net/emacsconf2020.html
Reply | Threaded
Open this post in threaded view
|

Re: Emacs <-> Lua via Emacs's dynamic modules

nerditation
On 2021/3/5 13:02, Eduardo Ochs wrote:

> Hi people,
>
> are you aware of any attempts to link Emacs and Lua using this?
>
>   http://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html
>   http://www.gnu.org/software/emacs/manual/html_node/elisp/Writing-Dynamic-Modules.html
>
> Even a barebones prototype that only allows sending a string a Lua and
> then have Lua send back a string to Emacs would be useful to me...
>
> Here is an example of an Emacs library that is partly implemented in C:
>
>   https://github.com/akermu/emacs-libvterm
>   https://emacsconf.org/2020/talks/30/
>
> Cheers and TIA,
>   Eduardo Ochs
>   http://angg.twu.net/luaforth.html
>   http://angg.twu.net/emacsconf2020.html
>

the emacs API is very much like JNI invocation interface. we are not manipulating the
elisp state directly (like how Lua does), instead we are supposed to use the lisp functions
like `defalias`.

here's a minimal working example I put together following the emacs documentation.
note version check and error handling are omitted for simplicity sake.

```C++
// emlua.cpp - a emacs module that runs Lua code
// g++ -IZ:/emacs/include -IZ:/Lua/include -shared emlua -o emlua.dll -LZ:/Lua/lib -llua
#include <vector>
#include <emacs-module.h>
#include <lua.hpp>

int plugin_is_GPL_compatible;

// TODO: convert lua values to elisp values in a meaningful way.
// PLACEHOLDER: call `luaL_tolstring` on everything
static emacs_value lua_to_elisp(lua_State *L, emacs_env *env, int i) {
        size_t size;
        auto s = luaL_tolstring(L, i, &size);
        return env->make_string(env, s, size);
}

#define EMACS_ENV_KEY "*emacs_env"

// ef_xxx is elisp function so uses emacs-module-func protocol
// basically a wrapper around the Lua `dostring` function
// returns a vector containing the multiple (possibly zero) return values (called `tostring` on them) of the Lua code
// returns an error message on failure
static emacs_value ef_lua_dostring(emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
        // closure data is lua_State
        lua_State *L = (lua_State *)data;
        // the env is valid on for this callstack
        lua_pushlightuserdata(L, env);
        lua_setfield(L, LUA_REGISTRYINDEX, EMACS_ENV_KEY);
        // string length: emacs uses signed type (ptrdiff_t), Lua uses unsigned type (size_t)
        ptrdiff_t len = 0;
        // emacs didn't provide API to `borrow` the string
        // we are forced to make a copy and then Lua will copy it again
        env->copy_string_contents(env, args[0], nullptr, &len);
        auto buffer = std::vector<char>(len);
        env->copy_string_contents(env, args[0], buffer.data(), &len);
        //assert(buffer.back() == '\0');
        auto status = luaL_dostring(L, buffer.data());
        if (status != LUA_OK) {
                auto ret = lua_to_elisp(L, env, -1);
                lua_settop(L, 0);
                return ret;
        }
        auto multret = std::vector<emacs_value>{};
        int retcount = lua_gettop(L);
        multret.reserve(retcount);
        for (int i = 1; i <= retcount; ++i) {
                multret.push_back(lua_to_elisp(L, env, i));
        }
        lua_settop(L, 0);
        return env->funcall(env, env->intern(env, "vector"), multret.size(), multret.data());
}

// lf_xxx is lua function so use lua_CFunction protocol
static int lf_message(lua_State *L)
{
        lua_getfield(L, LUA_REGISTRYINDEX, EMACS_ENV_KEY);
        auto *env = (emacs_env *)lua_touserdata(L, -1);
        size_t size;
        auto s = luaL_tolstring(L, 1, &size);
        emacs_value args[1] = {env->make_string(env, s, size)};
        env->funcall(env, env->intern(env, "message"), 1, args);
        return 0;
};

extern "C" {
int emacs_module_init(struct emacs_runtime *ert) noexcept
{
        emacs_env *env = ert->get_environment(ert);
        lua_State *L = luaL_newstate();
        luaL_openlibs(L);
        // register Lua callable function(s)
        lua_pushcfunction(L, lf_message);
        lua_setglobal(L, "message");
        // register elisp callable function(s)
        emacs_value func = env->make_function(
                        env,
                        1, // min_arity,
                        1, // max_arity,
                        &ef_lua_dostring,
                        "run string as Lua code",
                        L
                        );
        emacs_value symbol = env->intern(env, "emlua-dostring");
        emacs_value args[] = {symbol, func};
        env->funcall(env, env->intern(env, "defalias"), 2, args);
        return 0;
}
} // extern "C"

```