Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added
- Guards for encoded Lua values in `deflua` functions
- `is_table/1`
- `is_userdata/1`
- `is_lua_func/1`
- `is_erl_func/1`
- `is_mfa/1`

### Fixed
- `deflua` function can now specify guards when using or not using state

Expand Down
52 changes: 51 additions & 1 deletion lib/lua/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,22 @@ defmodule Lua.API do
~LUA[print("Hello at install time!")]c
end
end

## Guards

When doing `use Lua.API`, we also import the guards documented in this API.
This can be useful for having different function heads that match on encoded
values. E.g.

deflua say_type(value) when is_table(value), do: "table"
deflua say_type(value) when is_userdata(value), do: "table"

Keep in mind that if you want to work with values passed to `deflua` functions,
they still need to be decoded first.
"""

require Record

defmacro __using__(opts) do
scope = opts |> Keyword.get(:scope, "") |> String.split(".", trim: true)

Expand All @@ -85,7 +99,17 @@ defmodule Lua.API do
@before_compile Lua.API

import Lua.API,
only: [runtime_exception!: 1, deflua: 2, deflua: 3, validate_func!: 3]
only: [
runtime_exception!: 1,
deflua: 2,
deflua: 3,
validate_func!: 3,
is_table: 1,
is_userdata: 1,
is_lua_func: 1,
is_erl_func: 1,
is_mfa: 1
]

@impl Lua.API
def scope do
Expand All @@ -100,6 +124,32 @@ defmodule Lua.API do
@callback install(Lua.t(), scope_def(), any()) :: Lua.t() | Lua.Chunk.t() | String.t()
@optional_callbacks [install: 3]

@doc """
Is the value a reference to a Lua table?

"""
defguard is_table(record) when Record.is_record(record, :tref)

@doc """
Is the value a reference to userdata?
"""
defguard is_userdata(record) when Record.is_record(record, :usdref)

@doc """
Is the value a reference to a Lua function?
"""
defguard is_lua_func(record) when Record.is_record(record, :funref)

@doc """
Is the value a reference to an Erlang / Elixir function?
"""
defguard is_erl_func(record) when Record.is_record(record, :erl_func)

@doc """
Is the value a reference to an Erlang / Elixir mfa?
"""
defguard is_mfa(record) when Record.is_record(record, :erl_mfa)

@doc """
Raises a runtime exception inside an API function, displaying contextual
information about where the exception was raised.
Expand Down
2 changes: 1 addition & 1 deletion lib/lua/util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ defmodule Lua.Util do
# take out when https://github.com/rvirding/luerl/pull/213
# is released
def encoded?(number) when is_number(number), do: true
def encoded?(table_ref) when Record.is_record(table_ref, :tref), do: true
def encoded?(record) when Record.is_record(record, :tref), do: true
def encoded?(record) when Record.is_record(record, :usdref), do: true
def encoded?(record) when Record.is_record(record, :funref), do: true
def encoded?(record) when Record.is_record(record, :erl_func), do: true
Expand Down
53 changes: 53 additions & 0 deletions test/lua/api_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -336,4 +336,57 @@ defmodule Lua.APITest do
assert {"not a int", _} = module.with_state(true, Lua.new())
end
end

describe "guards" do
test "can use in functions" do
assert [{module, _}] =
Code.compile_string("""
defmodule GuardCheck do
use Lua.API, scope: "guard"

deflua type(value) when is_table(value) do
"table"
end

deflua type(value) when is_userdata(value) do
"userdata"
end

deflua type(value) when is_lua_func(value) do
"lua function"
end

deflua type(value) when is_erl_func(value) do
"erl function"
end

deflua type(value) when is_mfa(value) do
"mfa"
end

deflua type(_value) do
"other"
end
end
""")

lua =
Lua.load_api(Lua.new(), module)
|> Lua.set!(["foo"], {:userdata, URI.parse("https://tvlabs.ai")})

assert {["table"], _} = Lua.eval!(lua, "return guard.type({})")
assert {["userdata"], _} = Lua.eval!(lua, "return guard.type(foo)")

assert {["lua function"], _} =
Lua.eval!(lua, """
return guard.type(function()
return 42
end)
""")

assert {["erl function"], _} = Lua.eval!(lua, "return guard.type(guard.type)")
assert {["mfa"], _} = Lua.eval!(lua, "return guard.type(string.lower)")
assert {["other"], _} = Lua.eval!(lua, "return guard.type(5)")
end
end
end