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
70 changes: 70 additions & 0 deletions src/glua.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -1164,3 +1164,73 @@ pub fn call_function_by_name(
use fun <- then(get(keys))
call_function(fun, args)
}

/// Gets the value at `keys` under the provided table.
///
/// This might trigger the `__index` metamethod.
///
/// ## Examples
///
/// ```gleam
/// glua.run(glua.new(), {
/// let fun = fn(_) {
/// glua.success([glua.string("fixed value")])
/// }
///
/// use tbl <- glua.then(glua.table([]))
/// use mt <- glua.then(glua.table([#(glua.string("__index"), glua.function(fun))]))
/// use _ <- glua.then(glua.call_function(lib.set_metatable(), [tbl, mt]))
///
/// glua.index(tbl, "a_key")
/// })
/// // -> Ok(#(_state, ["fixed value"]))
/// ```
pub fn index(table ref: Value, key key: String) -> Action(Value, e) {
Action(do_index(_, ref, key))
}

@external(erlang, "glua_ffi", "get_table_key")
fn do_index(
state: Lua,
ref: Value,
key: String,
) -> Result(#(Lua, Value), LuaError(e))

/// Sets `value` under `key` of the provided table.
///
/// This might trigger the `__newindex` metamethod.
///
/// ## Examples
///
/// ```gleam
/// glua.run(glua.new(), {
/// let fun = fn(_) {
/// glua.error("this is a read-only table")
/// }
///
/// use tbl <- glua.then(glua.table([]))
/// use mt <- glua.then(glua.table([#(glua.string("__newindex"), glua.function(fun))]))
/// use _ <- glua.then(glua.call_function(lib.set_metatable(), [tbl, mt]))
///
/// glua.new_index(tbl, "my_new_key", glua.string("my_new_value"))
/// })
/// // -> Error(glua.LuaRuntimeException(
/// exception: glua.ErrorCall("this is a read-only table", option.None),
/// state: _
/// ))
/// ```
pub fn new_index(
table ref: Value,
key key: String,
value val: Value,
) -> Action(Nil, e) {
Action(do_new_index(_, ref, key, val))
}

@external(erlang, "glua_ffi", "set_table_key")
fn do_new_index(
state: Lua,
ref: Value,
key: String,
val: Value,
) -> Result(#(Lua, Nil), LuaError(e))
20 changes: 18 additions & 2 deletions src/glua_ffi.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
-import(ttdict, [fold/3]).
-include_lib("luerl/include/luerl.hrl").

-export([get_stacktrace/1, dereference/2, coerce/1, coerce_nil/0, wrap_fun/1, decode_fun/1, sandbox_fun/1, get_table_keys/2,
get_private/2, set_table_keys/3, load/2, load_file/2, eval/2, eval_file/2,
-export([get_stacktrace/1, dereference/2, coerce/1, coerce_nil/0, wrap_fun/1, decode_fun/1, sandbox_fun/1, get_table_key/3,
get_table_keys/2, get_private/2, set_table_key/4, set_table_keys/3, load/2, load_file/2, eval/2, eval_file/2,
eval_chunk/2, call_function/3]).


Expand Down Expand Up @@ -238,6 +238,14 @@ decode_fun(Fun) ->
_ -> {error, fun(_) -> {action, fun(State) -> {ok, {State, nil}} end} end}
end.

get_table_key(Lua, Tref, Key) ->
try luerl_emul:get_table_key(Tref, Key, Lua) of
{nil, _St} -> {error, {key_not_found, [Key]}};
{Value, St} -> {ok, {St, Value}}
catch
error:{lua_error, _, _} = Err -> {error, map_error(Err)}
end.

get_table_keys(Lua, Keys) ->
case luerl:get_table_keys(Keys, Lua) of
{ok, nil, _} ->
Expand All @@ -248,6 +256,14 @@ get_table_keys(Lua, Keys) ->
to_gleam(Other)
end.

set_table_key(Lua, Tref, Key, Value) ->
try
St = luerl_emul:set_table_key(Tref, Key, Value, Lua),
{ok, {St, nil}}
catch
error:{lua_error, _, _} = Err -> {error, map_error(Err)}
end.

set_table_keys(Lua, Keys, Value) ->
to_gleam(luerl:set_table_keys(Keys, Value, Lua)).

Expand Down
142 changes: 142 additions & 0 deletions test/glua_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import gleam/pair
import gleam/result
import gleeunit
import glua
import glua/lib

pub fn main() -> Nil {
gleeunit.main()
Expand Down Expand Up @@ -906,3 +907,144 @@ pub fn decode_function_test() {

assert exn == glua.BadArith("*", ["3", "true"])
}

pub fn index_test() {
let assert Ok(#(state, tbl)) =
glua.run(glua.new(), {
glua.table([#(glua.string("a_key"), glua.string("a value"))])
})

assert glua.run(
state,
glua.index(tbl, "a_key") |> glua.returning(decode.string),
)
|> result.map(pair.second)
== Ok("a value")

assert glua.run(state, {
use tbl <- glua.then(
glua.table([#(glua.string("a_key"), glua.string("a value"))]),
)
glua.index(tbl, "other_key")
})
== Error(glua.KeyNotFound(["other_key"]))

let assert Ok(#(state, tbl)) =
glua.run(glua.new(), {
use tbl <- glua.then(glua.table([]))
use mt <- glua.then(
glua.table([
#(
glua.string("__index"),
glua.function(fn(args) {
let assert [_table, key] = args

use k <- glua.then(glua.dereference(key, decode.string))
case int.parse(k) {
Ok(_) -> "integer"
Error(_) -> "other"
}
|> glua.string
|> list.wrap
|> glua.success
}),
),
]),
)

use _ <- glua.then(
glua.call_function_by_name(["setmetatable"], [tbl, mt]),
)
glua.success(tbl)
})

assert glua.run(state, glua.index(tbl, "1") |> glua.returning(decode.string))
|> result.map(pair.second)
== Ok("integer")

assert glua.run(state, glua.index(tbl, "a") |> glua.returning(decode.string))
|> result.map(pair.second)
== Ok("other")

assert glua.run(state, glua.index(tbl, "2") |> glua.returning(decode.int))
|> result.map(pair.second)
== Error(
glua.UnexpectedResultType([decode.DecodeError("Int", "String", [])]),
)
}

pub fn new_index_test() {
let assert Ok(#(state, tbl)) = glua.run(glua.new(), { glua.table([]) })

assert glua.run(state, {
use _ <- glua.then(glua.new_index(tbl, "a_key", glua.int(1)))
use ref <- glua.then(glua.index(tbl, "a_key"))
glua.dereference(ref, decode.int)
})
|> result.map(pair.second)
== Ok(1)

let assert Ok(#(state, tbl)) =
glua.run(glua.new(), {
use tbl <- glua.then(glua.table([]))
use mt <- glua.then(
glua.table([
#(
glua.string("__newindex"),
glua.function(fn(args) {
let assert [table, key, value] = args

use i <- glua.then(glua.dereference(value, decode.int))

let value = glua.int(i * i)
glua.call_function(lib.raw_set(), [table, key, value])
}),
),
]),
)

use _ <- glua.then(
glua.call_function_by_name(["setmetatable"], [tbl, mt]),
)
glua.success(tbl)
})

assert glua.run(state, {
use _ <- glua.then(glua.new_index(tbl, "a_key", glua.int(6)))
use ref <- glua.then(glua.index(tbl, "a_key"))
glua.dereference(ref, decode.int)
})
|> result.map(pair.second)
== Ok(36)

assert glua.run(state, {
use _ <- glua.then(glua.new_index(tbl, "other_key", glua.int(7)))
use ref <- glua.then(glua.index(tbl, "other_key"))
glua.dereference(ref, decode.int)
})
|> result.map(pair.second)
== Ok(49)

let assert Ok(#(state, tbl)) =
glua.run(glua.new(), {
use tbl <- glua.then(glua.table([]))
use mt <- glua.then(
glua.table([
#(
glua.string("__newindex"),
glua.function(fn(_) { glua.error("read-only table") }),
),
]),
)

use _ <- glua.then(
glua.call_function_by_name(["setmetatable"], [tbl, mt]),
)
glua.success(tbl)
})

let assert Error(glua.LuaRuntimeException(
exception: glua.ErrorCall(message: "read-only table", level: _),
state: _,
)) = glua.run(state, glua.new_index(tbl, "my_key", glua.string("my value")))
}