Skip to content

Commit 3875b35

Browse files
authored
feat: guards for encoded Lua values (#85)
1 parent 482a6c4 commit 3875b35

File tree

4 files changed

+113
-2
lines changed

4 files changed

+113
-2
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
- Guards for encoded Lua values in `deflua` functions
12+
- `is_table/1`
13+
- `is_userdata/1`
14+
- `is_lua_func/1`
15+
- `is_erl_func/1`
16+
- `is_mfa/1`
17+
1018
### Fixed
1119
- `deflua` function can now specify guards when using or not using state
1220

lib/lua/api.ex

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,22 @@ defmodule Lua.API do
7474
~LUA[print("Hello at install time!")]c
7575
end
7676
end
77+
78+
## Guards
79+
80+
When doing `use Lua.API`, we also import the guards documented in this API.
81+
This can be useful for having different function heads that match on encoded
82+
values. E.g.
83+
84+
deflua say_type(value) when is_table(value), do: "table"
85+
deflua say_type(value) when is_userdata(value), do: "table"
86+
87+
Keep in mind that if you want to work with values passed to `deflua` functions,
88+
they still need to be decoded first.
7789
"""
7890

91+
require Record
92+
7993
defmacro __using__(opts) do
8094
scope = opts |> Keyword.get(:scope, "") |> String.split(".", trim: true)
8195

@@ -85,7 +99,17 @@ defmodule Lua.API do
8599
@before_compile Lua.API
86100

87101
import Lua.API,
88-
only: [runtime_exception!: 1, deflua: 2, deflua: 3, validate_func!: 3]
102+
only: [
103+
runtime_exception!: 1,
104+
deflua: 2,
105+
deflua: 3,
106+
validate_func!: 3,
107+
is_table: 1,
108+
is_userdata: 1,
109+
is_lua_func: 1,
110+
is_erl_func: 1,
111+
is_mfa: 1
112+
]
89113

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

127+
@doc """
128+
Is the value a reference to a Lua table?
129+
130+
"""
131+
defguard is_table(record) when Record.is_record(record, :tref)
132+
133+
@doc """
134+
Is the value a reference to userdata?
135+
"""
136+
defguard is_userdata(record) when Record.is_record(record, :usdref)
137+
138+
@doc """
139+
Is the value a reference to a Lua function?
140+
"""
141+
defguard is_lua_func(record) when Record.is_record(record, :funref)
142+
143+
@doc """
144+
Is the value a reference to an Erlang / Elixir function?
145+
"""
146+
defguard is_erl_func(record) when Record.is_record(record, :erl_func)
147+
148+
@doc """
149+
Is the value a reference to an Erlang / Elixir mfa?
150+
"""
151+
defguard is_mfa(record) when Record.is_record(record, :erl_mfa)
152+
103153
@doc """
104154
Raises a runtime exception inside an API function, displaying contextual
105155
information about where the exception was raised.

lib/lua/util.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ defmodule Lua.Util do
2626
# take out when https://github.com/rvirding/luerl/pull/213
2727
# is released
2828
def encoded?(number) when is_number(number), do: true
29-
def encoded?(table_ref) when Record.is_record(table_ref, :tref), do: true
29+
def encoded?(record) when Record.is_record(record, :tref), do: true
3030
def encoded?(record) when Record.is_record(record, :usdref), do: true
3131
def encoded?(record) when Record.is_record(record, :funref), do: true
3232
def encoded?(record) when Record.is_record(record, :erl_func), do: true

test/lua/api_test.exs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,4 +336,57 @@ defmodule Lua.APITest do
336336
assert {"not a int", _} = module.with_state(true, Lua.new())
337337
end
338338
end
339+
340+
describe "guards" do
341+
test "can use in functions" do
342+
assert [{module, _}] =
343+
Code.compile_string("""
344+
defmodule GuardCheck do
345+
use Lua.API, scope: "guard"
346+
347+
deflua type(value) when is_table(value) do
348+
"table"
349+
end
350+
351+
deflua type(value) when is_userdata(value) do
352+
"userdata"
353+
end
354+
355+
deflua type(value) when is_lua_func(value) do
356+
"lua function"
357+
end
358+
359+
deflua type(value) when is_erl_func(value) do
360+
"erl function"
361+
end
362+
363+
deflua type(value) when is_mfa(value) do
364+
"mfa"
365+
end
366+
367+
deflua type(_value) do
368+
"other"
369+
end
370+
end
371+
""")
372+
373+
lua =
374+
Lua.load_api(Lua.new(), module)
375+
|> Lua.set!(["foo"], {:userdata, URI.parse("https://tvlabs.ai")})
376+
377+
assert {["table"], _} = Lua.eval!(lua, "return guard.type({})")
378+
assert {["userdata"], _} = Lua.eval!(lua, "return guard.type(foo)")
379+
380+
assert {["lua function"], _} =
381+
Lua.eval!(lua, """
382+
return guard.type(function()
383+
return 42
384+
end)
385+
""")
386+
387+
assert {["erl function"], _} = Lua.eval!(lua, "return guard.type(guard.type)")
388+
assert {["mfa"], _} = Lua.eval!(lua, "return guard.type(string.lower)")
389+
assert {["other"], _} = Lua.eval!(lua, "return guard.type(5)")
390+
end
391+
end
339392
end

0 commit comments

Comments
 (0)