Skip to content

Commit

Permalink
Add install callback to Lua.API (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
davydog187 authored Apr 12, 2024
1 parent 8d5e0c0 commit 2b3a408
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 5 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

``` elixir
iex> {[4], _} =
...> Lua.eval!("""
...> Lua.eval!(~LUA"""
...> return 2 + 2
...> """)
Expand All @@ -42,7 +42,7 @@ end
lua = Lua.new() |> Lua.load_api(MyAPI)
{[10], _} =
Lua.eval!(lua, """
Lua.eval!(lua, ~LUA"""
return double(5)
""")

Expand All @@ -64,7 +64,7 @@ end

lua = Lua.new() |> Lua.load_api(MyAPI)

{["wow"], _} = Lua.eval!(lua, "return example.foo(\"WOW\")")
{["wow"], _} = Lua.eval!(lua, ~LUA"return example.foo(\"WOW\")")
```

## Modify Lua state from Elixir
Expand Down
7 changes: 5 additions & 2 deletions lib/lua.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule Lua do
|> String.split("<!-- MDOC !-->")
|> Enum.fetch!(1)

@type t :: %__MODULE__{}

defstruct [:state]

alias Luerl.New, as: Luerl
Expand Down Expand Up @@ -358,9 +360,9 @@ defmodule Lua do
end

@doc """
Inject functions written with the `deflua` macro into the Lua runtime
Inject functions written with the `deflua` macro into the Lua runtime.
Similar to `:luerl_new.load_module/3` but without the `install` callback
See `Lua.API` for more information on writing api modules
"""
def load_api(lua, module, scope \\ nil) do
funcs = :functions |> module.__info__() |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
Expand All @@ -380,6 +382,7 @@ defmodule Lua do
end
)
end)
|> Lua.API.install(module)
end

defp wrap_variadic_function(module, function_name, with_state?) do
Expand Down
53 changes: 53 additions & 0 deletions lib/lua/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,37 @@ defmodule Lua.API do
def baz(v), do: v
end
## Installing an API
A `Lua.API` can provide an optional `install/1` 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
Lua script to be evaluated, or return a new `t:Lua.t/0`
defmodule WithInstall do
use Lua.API, scope: "install"
@impl Lua.API
def install(lua) do
Lua.set!(lua, [:foo], "bar")
end
end
If you don't need to write Elixir, but want to execute some Lua
to setup global variables, modify state, or expose some additonal
APIs, you can simply return a Lua script directly
defmodule WithLua do
use Lua.API, scope: "whoa"
import Lua
@impl Lua.API
def install(_lua) do
~LUA[print("Hello at install time!")]
end
end
"""

defmacro __using__(opts) do
Expand All @@ -63,6 +94,8 @@ defmodule Lua.API do
end

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

@doc """
Raises a runtime exception inside an API function, displaying contextual
Expand Down Expand Up @@ -120,6 +153,26 @@ defmodule Lua.API do
end
end

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

code when is_binary(code) ->
{_, lua} = Lua.eval!(lua, code)
lua

other ->
raise Lua.RuntimeException,
"Lua.API.install/1 must return %Lua{} or a Lua literal, got #{inspect(other)}"
end
else
lua
end
end

defmacro __before_compile__(env) do
attributes =
env.module
Expand Down
52 changes: 52 additions & 0 deletions test/lua/api_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,58 @@ defmodule Lua.APITest do

alias Lua

describe "install/1 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
{[ret], lua} = Lua.call_function!(lua, [:foo], [])
Lua.set!(lua, [:from_install], ret)
end
deflua foo do
"foo"
end
end
""")

lua = Lua.load_api(Lua.new(), module)

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

test "it can return lua code directly" do
assert [{module, _}] =
Code.compile_string("""
defmodule WithLua do
use Lua.API
import Lua
@impl Lua.API
def install(_lua) do
~LUA[whoa = "crazy"]
end
end
""")

lua = Lua.load_api(Lua.new(), module)

assert {["crazy"], _} =
Lua.eval!(lua, """
return whoa
""")
end
end

describe "deflua" do
test "can access the current lua state" do
assert [{module, _}] =
Expand Down

0 comments on commit 2b3a408

Please sign in to comment.