LuaPort

An Erlang/Elixir port for scripting application logic in Lua. Works with Lua and LuaJIT.

Use Erlang…

{ok, Pid, []} = luaport:spawn(some_id, "path/to/scripts"),
{ok, [6]} = luaport:call(Pid, multiply, [2, 3]).

…or Elixir…

{:ok, pid, []} = :luaport.spawn(:some_id, "path/to/scripts")
{:ok, [6]} = :luaport.call(pid, :multiply, [2, 3])

…to execute a Lua script.

function multiply(a, b)
  return a * b
end

Test

git clone https://github.com/asciibeats/luaport.git
cd luaport
rebar3 ct

Use

If you use Erlang and rebar3, add LuaPort as dependency to your rebar.config.

{deps, [
  {luaport, "~> 1.6"}
]}.

Or for Elixir and mix, add it to your mix.exs.

defp deps do
  [
    {:luaport, "~> 1.6"}
  ]
end

Create a Lua script at path/to/scripts called main.lua.

function subtract(a, b)
  return a - b
end

When using Erlang, don’t forget to start the application.

application:start(luaport),
{ok, Pid, []} = luaport:spawn(myid, "path/to/scripts"),
{ok, [42]} = luaport:call(Pid, subtract, [43, 1]),
luaport:despawn(myid),
application:stop(luaport).

With Elixir it will start automatically.

{:ok, pid, []} = :luaport.spawn(:myid, "path/to/scripts")
{:ok, [42]} = :luaport.call(pid, :subtract, [43, 1])
:luaport.despawn(:myid)

To return results on spawn and respawn, just add a return statement to your main.lua

function do()
  print('something')
end

return 23, 42

…and retrieve them like this:

{ok, Pid1, [23, 42]} = luaport:spawn(myid, "path/to/scripts"),
{ok, Pid2, [23, 42]} = luaport:respawn(myid).

To add static data to the port’s context, add a map as third argument to the spawn function.

{ok, Pid, []} = luaport:spawn(myid, "path/to/scripts", #{config => {what, ever}, greeting => <<"moin">>}).

The elements of that map will be available as global variables. Be careful not to choose colliding names, as these variables will be named after the maps keys.

local a, b = unpack(config)

function greet()
  print(greeting)
end

To push global variables into the context during runtime, use the push function.

luaport:push(myid, #{name => <<"til">>}).

To pull dynamic data into the context, you may provide a callback module as the fourth argument to spawn.

{ok, Pid, []} = luaport:spawn(myid, "path/to/scripts", #{}, callback).
-module(callback).

-export([init/2, print/1]).

init(String, Number) ->
  [#{string => String}, Number].

print(Message) ->
  io:format("Message: ~p~n", [Message]).

Calls and casts will automagically be mapped to the module’s function of the same name.

local map, number = port.call.init(&#39;sunshine&#39;, 49)
port.cast.print(&#39;some message&#39;)

If you want to insert or just execute some code during runtime, use the load function.

{ok, []} = luaport:load(Pid, <<"function something() return 666 end">>),
{ok, [666]} = luaport:call(Pid, something).
{ok, []} = luaport:load(Pid, <<"print(&#39;nice&#39;)">>).
{ok, [42]} = luaport:load(Pid, <<"return 42">>).

To be able to continuously call or cast functions after accidental or intended respawns, you could use {global, Name} or {local, Name} as reference to register the port.

{ok, Pid1, []} = luaport:spawn({local, myid}, "path/to/scripts"),
{ok, Pid2, []} = luaport:respawn({local, myid}),
luaport:cast({local, myid}, execute).

Requiring modules works normally. You may put a module.lua or module.so into path/to/scripts or any other path in Lua’s package.path or package.cpath, respectively.

local module = require(&#39;module&#39;)

Lua has no delayed call mechanism, therefore LuaPort provides an interface to Erlang’s timer functions. The first argument is the time to wait in milliseconds.

local tref = port.after(3000, function (str) print(str) end, &#39;call once, if not canceled&#39;)
port.cancel(tref)
local tref = port.interval(1000, function (str) print(str) end, &#39;call repeatedly until canceled&#39;)
port.cancel(tref)

Finally, to just suspend execution for a while, use the sleep function.

port.sleep(2000)

Quirks

Since Erlang and Lua datatypes do not align too nicely, there are some things to consider.

Translations

Erlang Elixir Lua Notes
23 23 23
“abc” ‘abc’ {97, 98, 99} Erlang strings are lists
<<”abc”>> “abc” ‘abc’
[1, 2] [1, 2] {1, 2} has metatype ‘list’
{3, 4} {3, 4} {3, 4} has metatype ‘tuple’
#{5 => 6} %{5 => 6} {[5] = 6} has no metatype
true true true
false false false
nil nil nil
atom :atom ‘atom’

Helpers

Function Description
port.aslist(t) set metatype ‘list’
port.astuple(t) set metatype ‘tuple’
port.asmap(t) unset metatype
port.islist(v) if metatype ‘list’
port.istuple(v) if metatype ‘tuple’
port.ismap(v) if no metatype

Notes