Skip to content

Commit

Permalink
Change Lua.API.install/1 callback to Lua.API.install/3
Browse files Browse the repository at this point in the history
Changes the arity of the API, passing the scope and data to the
`install/3` callback, allowing the install callback to capture runtime data from APIs, databases, or elsewhere in the API scope for further usage
  • Loading branch information
davydog187 committed Sep 8, 2024
1 parent d5b9e05 commit 125e5cd
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 24 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,18 @@
## Exposing Elixir functions to Lua
`Lua` provides the `deflua` macro for exposing Elixir functions to Lua
The simplest way to expose an Elixir function to in Lua is using the `Lua.set!/3` function
``` elixir
lua =
Lua.set!(Lua.new(), [:sum], fn args ->
[Enum.sum(args)]
end)
{[10], _} = Lua.eval!(lua, ~LUA"return sum(1, 2, 3, 4)"c)
```
For easily expressing APIs, `Lua` provides the `deflua` macro for exposing Elixir functions to Lua
``` elixir
defmodule MyAPI do
Expand All @@ -46,7 +57,6 @@ lua = Lua.new() |> Lua.load_api(MyAPI)
Lua.eval!(lua, ~LUA"""
return double(5)
""")

```

## Calling Lua functions from Elixir
Expand Down
12 changes: 9 additions & 3 deletions lib/lua.ex
Original file line number Diff line number Diff line change
Expand Up @@ -462,10 +462,16 @@ defmodule Lua do
Inject functions written with the `deflua` macro into the Lua runtime.
See `Lua.API` for more information on writing api modules
### Options
* `:scope` - (optional) scope, overriding whatever is provided in `use Lua.API, scope: ...`
* `:data` - (optional) - data to be passed to the Lua.API.install/3 callback
"""
def load_api(lua, module, scope \\ nil) do
def load_api(lua, module, opts \\ []) do
opts = Keyword.validate!(opts, [:scope, :data])
funcs = :functions |> module.__info__() |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
scope = scope || module.scope()
scope = opts[:scope] || module.scope()

lua = ensure_scope!(lua, scope)

Expand All @@ -483,7 +489,7 @@ defmodule Lua do
end
)
end)
|> Lua.API.install(module)
|> Lua.API.install(module, scope, opts[:data])
end

defp wrap_variadic_function(module, function_name, with_state?) do
Expand Down
22 changes: 12 additions & 10 deletions lib/lua/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ defmodule Lua.API do
## Installing an API
A `Lua.API` can provide an optional `install/1` callback, which
A `Lua.API` can provide an optional `install/3` callback, which
can run arbitrary Lua code or change the `Lua` state in any way.
An `install/1` callback takes a `t:Lua.t/0` and should either return a
An `install/3` callback takes a `t:Lua.t/0` and should either return a
Lua script to be evaluated, a `t:Lua.Chunk.t/0`, or return a new `t:Lua.t/0`
defmodule WithInstall do
use Lua.API, scope: "install"
@impl Lua.API
def install(lua) do
def install(lua, _scope, _data) do
Lua.set!(lua, [:foo], "bar")
end
end
Expand All @@ -70,7 +70,7 @@ defmodule Lua.API do
import Lua
@impl Lua.API
def install(_lua) do
def install(_lua, _scope, _data) do
~LUA[print("Hello at install time!")]c
end
end
Expand All @@ -94,9 +94,11 @@ defmodule Lua.API do
end
end

@callback scope :: list(String.t())
@callback install(Lua.t()) :: Lua.t() | String.t()
@optional_callbacks [install: 1]
@type scope_def :: list(String.t())

@callback scope :: scope_def()
@callback install(Lua.t(), scope_def(), any()) :: Lua.t() | String.t()
@optional_callbacks [install: 3]

@doc """
Raises a runtime exception inside an API function, displaying contextual
Expand Down Expand Up @@ -199,9 +201,9 @@ defmodule Lua.API do
end

@doc false
def install(lua, module) do
if function_exported?(module, :install, 1) do
case module.install(lua) do
def install(lua, module, scope, data) do
if function_exported?(module, :install, 3) do
case module.install(lua, scope, data) do
%Lua{} = lua ->
lua

Expand Down
8 changes: 4 additions & 4 deletions test/lua/api_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ defmodule Lua.APITest do

alias Lua

describe "install/1 callback" do
describe "install/3 callback" do
test "it can modify global lua state" do
assert [{module, _}] =
Code.compile_string("""
defmodule WithInstall do
use Lua.API
@impl Lua.API
def install(lua) do
def install(lua, _scope, _data) do
{[ret], lua} = Lua.call_function!(lua, [:foo], [])
Lua.set!(lua, [:from_install], ret)
Expand Down Expand Up @@ -40,7 +40,7 @@ defmodule Lua.APITest do
import Lua
@impl Lua.API
def install(_lua) do
def install(_lua, _scope, _data) do
~LUA[whoa = "crazy"]
end
end
Expand All @@ -63,7 +63,7 @@ defmodule Lua.APITest do
import Lua
@impl Lua.API
def install(_lua) do
def install(_lua, _scope, _data) do
~LUA[whoa = "crazy"]c
end
end
Expand Down
42 changes: 37 additions & 5 deletions test/lua_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ defmodule LuaTest do

assert {[], %Lua{}} = Lua.eval!(lua, "foo(5)")
assert {[2], %Lua{}} = Lua.eval!(lua, "return foo(1)")

lua =
Lua.set!(Lua.new(), [:sum], fn args ->
[Enum.sum(args)]
end)

assert {[10], _} = Lua.eval!(lua, ~LUA"return sum(1, 2, 3, 4)"c)
end

test "it can evaluate chunks" do
Expand Down Expand Up @@ -605,6 +612,19 @@ defmodule LuaTest do
use Lua.API
end

defmodule WithInstall do
use Lua.API

@impl Lua.API
def install(lua, scope, data) do
if data do
Lua.set!(lua, scope ++ [:foo], data)
else
~LUA"foo = 42"c
end
end
end

setup do
{:ok, lua: Lua.new()}
end
Expand All @@ -615,22 +635,22 @@ defmodule LuaTest do
end

test "injects a scoped Elixir module functions into the Lua runtime", %{lua: lua} do
lua = Lua.load_api(lua, TestModule, ["scope"])
lua = Lua.load_api(lua, TestModule, scope: ["scope"])
assert {["test"], _} = Lua.eval!(lua, "return scope.foo('test')")
end

test "inject a variadic function", %{lua: lua} do
lua = Lua.load_api(lua, TestModule, ["scope"])
lua = Lua.load_api(lua, TestModule, scope: ["scope"])
assert {["a-b-c"], _} = Lua.eval!(lua, "return scope.bar('a', 'b', 'c')")
end

test "inject a variadic function with state", %{lua: lua} do
lua = Lua.load_api(lua, TestModule, ["scope"])
lua = Lua.load_api(lua, TestModule, scope: ["scope"])
assert {["a", "b", "c"], _} = Lua.eval!(lua, "return scope.with_state('a', 'b', 'c')")
end

test "injects Elixir functions that have multiple arities", %{lua: lua} do
lua = Lua.load_api(lua, TestModule, ["scope"])
lua = Lua.load_api(lua, TestModule, scope: ["scope"])

assert {["a default"], _} = Lua.eval!(lua, "return scope.test(\"a\")")
assert {["a b"], _} = Lua.eval!(lua, "return scope.test(\"a\", \"b\")")
Expand All @@ -649,6 +669,18 @@ defmodule LuaTest do

{[22], _} = Lua.eval!(lua, "return global_var")
end

test "it will call the install callback", %{lua: lua} do
lua = Lua.load_api(lua, WithInstall)

assert {[42], _lua} = Lua.eval!(lua, "return foo")
end

test "it can pass data to the install callback", %{lua: lua} do
lua = Lua.load_api(lua, WithInstall, data: "bananas")

assert {["bananas"], _lua} = Lua.eval!(lua, "return foo")
end
end

describe "examples" do
Expand Down Expand Up @@ -701,7 +733,7 @@ defmodule LuaTest do
end

setup do
%{lua: Lua.new() |> Lua.load_api(Examples, ["example"])}
%{lua: Lua.new() |> Lua.load_api(Examples, scope: ["example"])}
end

test "can work with numbers", %{lua: lua} do
Expand Down

0 comments on commit 125e5cd

Please sign in to comment.