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
33 changes: 20 additions & 13 deletions src/glua.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import gleam/dynamic/decode
import gleam/int
import gleam/list
import gleam/option
import gleam/pair
import gleam/result
import gleam/string

Expand Down Expand Up @@ -247,8 +248,12 @@ pub fn int(v: Int) -> Value
@external(erlang, "glua_ffi", "coerce")
pub fn float(v: Float) -> Value

@external(erlang, "glua_ffi", "coerce")
pub fn table(values: List(#(Value, Value))) -> Value
pub fn table(lua: Lua, values: List(#(Value, Value))) -> #(Lua, Value) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this do what luerl:encode but manually?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of nested tables. If we try to call luerl:encode on a list that already have encoded values inside it will result in a runtime error.

do_table(values, lua) |> pair.swap
}

@external(erlang, "luerl_heap", "alloc_table")
fn do_table(values: List(#(Value, Value)), lua: Lua) -> #(Value, Lua)

pub fn table_decoder(
keys_decoder: decode.Decoder(a),
Expand All @@ -266,8 +271,7 @@ pub fn table_decoder(
pub fn function(
f: fn(Lua, List(dynamic.Dynamic)) -> #(Lua, List(Value)),
) -> Value {
// we need a little wrapper for functions to satisfy luerl's order of arguments and return value type
wrap_function(f)
do_function(f)
}

pub fn list(encoder: fn(a) -> Value, values: List(a)) -> List(Value) {
Expand All @@ -292,10 +296,11 @@ pub fn list(encoder: fn(a) -> Value, values: List(a)) -> List(Value) {
/// }
///
/// let state = glua.new()
/// let #(state, userdata) = glua.userdata(state, User(name: "Jhon Doe", is_admin: False))
/// let assert Ok(state) = glua.set(
/// state:,
/// keys: ["a_user"],
/// value: glua.userdata(User(name: "Jhon Doe", is_admin: False))
/// value: userdata
/// )
///
/// let assert Ok(#(_, [result])) = glua.eval(state:, code: "return a_user", using: user_decoder)
Expand All @@ -308,20 +313,25 @@ pub fn list(encoder: fn(a) -> Value, values: List(a)) -> List(Value) {
/// }
///
/// let state = glua.new()
/// let #(state, userdata) = glua.userdata(state, Person(name: "Lucy", email: "lucy@example.com"))
/// let assert Ok(lua) = glua.set(
/// state:,
/// keys: ["lucy"],
/// value: glua.userdata(Person(name: "Lucy", email: "lucy@example.com"))
/// value: userdata
/// )
///
/// let assert Error(glua.LuaRuntimeException(glua.IllegalIndex(_), _)) =
/// glua.eval(state:, code: "return lucy.email", using: decode.string)
/// ```
@external(erlang, "glua_ffi", "coerce_userdata")
pub fn userdata(v: anything) -> Value
pub fn userdata(lua: Lua, v: anything) -> #(Lua, Value) {
do_userdata(v, lua) |> pair.swap
}

@external(erlang, "luerl_heap", "alloc_userdata")
fn do_userdata(v: anything, lua: Lua) -> #(Value, Lua)

@external(erlang, "glua_ffi", "wrap_fun")
fn wrap_function(
fn do_function(
fun: fn(Lua, List(dynamic.Dynamic)) -> #(Lua, List(Value)),
) -> Value

Expand Down Expand Up @@ -511,7 +521,7 @@ pub fn set(
Ok(_) -> Ok(#(keys, lua))

Error(KeyNotFound(_)) -> {
let #(tbl, lua) = alloc_table([], lua)
let #(tbl, lua) = do_table([], lua)
do_set(lua, keys, tbl)
|> result.map(fn(lua) { #(keys, lua) })
}
Expand Down Expand Up @@ -578,9 +588,6 @@ pub fn set_lua_paths(
set(lua, ["package", "path"], paths)
}

@external(erlang, "luerl_emul", "alloc_table")
fn alloc_table(content: List(a), lua: Lua) -> #(a, Lua)

@external(erlang, "glua_ffi", "get_table_keys_dec")
fn do_get(lua: Lua, keys: List(String)) -> Result(dynamic.Dynamic, LuaError)

Expand Down
54 changes: 10 additions & 44 deletions src/glua_ffi.erl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-module(glua_ffi).
-import(luerl_lib, [lua_error/2]).

-export([get_stacktrace/1, coerce/1, coerce_nil/0, coerce_userdata/1, wrap_fun/1, sandbox_fun/1, get_table_keys/2, get_table_keys_dec/2,
-export([get_stacktrace/1, coerce/1, coerce_nil/0, wrap_fun/1, sandbox_fun/1, get_table_keys/2, get_table_keys_dec/2,
get_private/2, set_table_keys/3, load/2, load_file/2, eval/2, eval_dec/2, eval_file/2,
eval_file_dec/2, eval_chunk/2, eval_chunk_dec/2, call_function/3, call_function_dec/3]).

Expand Down Expand Up @@ -30,33 +30,6 @@ to_gleam(Value) ->
{error, {unknown_error, nil}}
end.

%% helper to determine if a value is encoded or not
%% borrowed from https://github.com/tv-labs/lua/blob/main/lib/lua/util.ex#L19-L35
is_encoded(nil) ->
true;
is_encoded(true) ->
true;
is_encoded(false) ->
true;
is_encoded(Binary) when is_binary(Binary) ->
true;
is_encoded(N) when is_number(N) ->
true;
is_encoded({tref,_}) ->
true;
is_encoded({usrdef,_}) ->
true;
is_encoded({eref,_}) ->
true;
is_encoded({funref,_,_}) ->
true;
is_encoded({erl_func,_}) ->
true;
is_encoded({erl_mfa,_,_,_}) ->
true;
is_encoded(_) ->
false.

map_error({error, Errors, _}) ->
{lua_compile_failure, lists:map(fun map_compile_error/1, Errors)};
map_error({lua_error, {illegal_index, Value, Index}, State}) ->
Expand Down Expand Up @@ -190,18 +163,17 @@ coerce(X) ->
coerce_nil() ->
nil.

coerce_userdata(X) ->
{userdata, X}.

wrap_fun(Fun) ->
fun(Args, State) ->
{erl_func, fun(Args, State) ->
Decoded = luerl:decode_list(Args, State),
{NewState, Ret} = Fun(State, Decoded),
luerl:encode_list(Ret, NewState)
end.
{Ret, NewState}
end}.

sandbox_fun(Msg) ->
fun(_, State) -> {error, map_error(lua_error({error_call, [Msg]}, State))} end.
{erl_func, fun(_, State) ->
{error, map_error(lua_error({error_call, [Msg]}, State))}
end}.

get_table_keys(Lua, Keys) ->
case luerl:get_table_keys(Keys, Lua) of
Expand All @@ -224,11 +196,7 @@ get_table_keys_dec(Lua, Keys) ->
end.

set_table_keys(Lua, Keys, Value) ->
SetFun = case is_encoded(Value) of
true -> fun luerl:set_table_keys/3;
false -> fun luerl:set_table_keys_dec/3
end,
to_gleam(SetFun(Keys, Value, Lua)).
to_gleam(luerl:set_table_keys(Keys, Value, Lua)).

load(Lua, Code) ->
to_gleam(luerl:load(
Expand Down Expand Up @@ -264,12 +232,10 @@ eval_file_dec(Lua, Path) ->
unicode:characters_to_list(Path), Lua)).

call_function(Lua, Fun, Args) ->
{EncodedArgs, State} = luerl:encode_list(Args, Lua),
to_gleam(luerl:call(Fun, EncodedArgs, State)).
to_gleam(luerl:call(Fun, Args, Lua)).

call_function_dec(Lua, Fun, Args) ->
{EncodedArgs, St1} = luerl:encode_list(Args, Lua),
case luerl:call(Fun, EncodedArgs, St1) of
case luerl:call(Fun, Args, Lua) of
{ok, Ret, St2} ->
Values = luerl:decode_list(Ret, St2),
{ok, {St2, Values}};
Expand Down
46 changes: 21 additions & 25 deletions test/glua_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ pub fn get_table_test() {
]
let cool_numbers =
glua.function(fn(lua, _params) {
let table =
let #(lua, table) =
glua.table(
lua,
my_table
|> list.map(fn(pair) { #(glua.string(pair.0), glua.int(pair.1)) }),
|> list.map(fn(pair) { #(glua.string(pair.0), glua.int(pair.1)) }),
)

#(lua, [table])
})

Expand Down Expand Up @@ -119,17 +121,11 @@ pub fn new_sandboxed_test() {
}

pub fn encoding_and_decoding_nested_tables_test() {
let nested_table = [
#(
glua.string("key"),
glua.table([
#(
glua.int(1),
glua.table([#(glua.string("deeper_key"), glua.string("deeper_value"))]),
),
]),
),
]
let lua = glua.new()
let #(lua, tb1) =
glua.table(lua, [#(glua.string("deeper_key"), glua.string("deeper_value"))])
let #(lua, tb2) = glua.table(lua, [#(glua.int(1), tb1)])
let #(lua, tb3) = glua.table(lua, [#(glua.string("key"), tb2)])

let keys = ["my_nested_table"]

Expand All @@ -141,9 +137,8 @@ pub fn encoding_and_decoding_nested_tables_test() {
glua.table_decoder(decode.string, decode.string),
),
)
let tbl = glua.table(nested_table)

let assert Ok(lua) = glua.set(state: glua.new(), keys:, value: tbl)
let assert Ok(lua) = glua.set(state: lua, keys:, value: tb3)

let assert Ok(result) =
glua.get(state: lua, keys:, using: nested_table_decoder)
Expand All @@ -157,22 +152,21 @@ pub type Userdata {

pub fn userdata_test() {
let lua = glua.new()
let userdata = Userdata("my-userdata", 1)
let #(lua, userdata) = glua.userdata(lua, Userdata("my-userdata", 1))
let userdata_decoder = {
use foo <- decode.field(1, decode.string)
use bar <- decode.field(2, decode.int)
decode.success(Userdata(foo:, bar:))
}

let assert Ok(lua) = glua.set(lua, ["my_userdata"], glua.userdata(userdata))
let assert Ok(lua) = glua.set(lua, ["my_userdata"], userdata)
let assert Ok(#(lua, [result])) =
glua.eval(lua, "return my_userdata", userdata_decoder)

assert result == userdata
assert result == Userdata("my-userdata", 1)

let userdata = Userdata("other_userdata", 2)
let assert Ok(lua) =
glua.set(lua, ["my_other_userdata"], glua.userdata(userdata))
let #(lua, userdata) = glua.userdata(lua, Userdata("other-userdata", 2))
let assert Ok(lua) = glua.set(lua, ["my_other_userdata"], userdata)
let assert Error(glua.LuaRuntimeException(glua.IllegalIndex(index, _), _)) =
glua.eval(lua, "return my_other_userdata.foo", decode.string)

Expand Down Expand Up @@ -236,8 +230,9 @@ pub fn set_test() {

let keys = ["math", "squares"]

let encoded =
let #(lua, encoded) =
glua.table(
lua,
numbers |> list.map(fn(pair) { #(glua.int(pair.0), glua.int(pair.1)) }),
)
let assert Ok(lua) = glua.set(lua, keys, encoded)
Expand All @@ -260,8 +255,9 @@ pub fn set_test() {
let encoded = glua.function(count_odd)
let assert Ok(lua) = glua.set(glua.new(), ["count_odd"], encoded)

let arg =
let #(lua, arg) =
glua.table(
lua,
list.index_map(list.range(1, 10), fn(i, n) {
#(glua.int(i + 1), glua.int(n))
}),
Expand All @@ -277,8 +273,8 @@ pub fn set_test() {

assert result == 5

let tbl =
glua.table([
let #(lua, tbl) =
glua.table(lua, [
#(
glua.string("is_even"),
glua.function(fn(lua, args) {
Expand Down