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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

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

## [v0.2.1] - 2025-05-14

### Added
Expand Down
31 changes: 25 additions & 6 deletions lib/lua/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ defmodule Lua.API do
@doc """
Defines a function that can be exposed in Lua through `Lua.load_api/3`

deflua add_two(number) do
deflua add_two(number) when is_number(number) do
number + 2
end

Expand All @@ -143,6 +143,18 @@ defmodule Lua.API do
{[], Lua.set!(lua, [key])}
end

## Using guards

Since `deflua` uses non-conventional syntax to receive the current state, make sure
you specifiy the `when` clause and guards first, e.g.

deflua set_int(key, value) when is_integer(value), state do
# Return nothing but modify the state
{[], Lua.set!(lua, [key])}
end

Specifyiing the `when` cluase and guards last will result in a confusing error message.

## Variadic functions

Technically, all Lua functions are variadic, which means they can receive
Expand All @@ -164,13 +176,20 @@ defmodule Lua.API do

"""
defmacro deflua(fa, state, rest) do
name =
case fa do
{:when, _, [{name, _, _} | _]} -> name
{name, _, _} -> name
end

{fa, _acc} =
Macro.prewalk(fa, false, fn
{name, context, args}, false -> {{name, context, args ++ List.wrap(state)}, true}
ast, true -> {ast, true}
end)
Macro.prewalk(fa, :ok, fn
{^name, context, args}, acc ->
{{name, context, args ++ List.wrap(state)}, acc}

{name, _, _} = fa
ast, acc ->
{ast, acc}
end)

quote do
@lua_function validate_func!(
Expand Down
36 changes: 36 additions & 0 deletions test/lua/api_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -299,5 +299,41 @@ defmodule Lua.APITest do
assert module.fail(1, 2) == 3
assert module.fail(1, "2") == "rescued"
end

test "deflua functions can have guards" do
assert [{module, _}] =
Code.compile_string("""
defmodule WithGuards do
use Lua.API

deflua has_a_guard(a) when is_integer(a) do
a
end

deflua has_a_guard(a) when is_binary(a) and not is_boolean(a) do
a
end

deflua has_a_guard(_) do
"not a int"
end

deflua with_state(a) when is_integer(a), state do
{a, state}
end

deflua with_state(_), state do
{"not a int", state}
end
end
""")

assert module.has_a_guard(1) == 1
assert module.has_a_guard("foo") == "foo"
assert module.has_a_guard(true) == "not a int"

assert {1, _} = module.with_state(1, Lua.new())
assert {"not a int", _} = module.with_state(true, Lua.new())
end
end
end