Skip to content

Commit 59210c8

Browse files
committed
Realize that only non library functions get decoded to functions
1 parent d3c4877 commit 59210c8

4 files changed

Lines changed: 121 additions & 113 deletions

File tree

src/glua.gleam

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,33 @@ pub type Value
5959
/// that will return references to the values instead of decoding them.
6060
pub type ValueRef
6161

62+
/// A decoded lua function
63+
/// Accepts decoded values and returns decoded values
64+
pub type LuaFunc {
65+
LuaFunc(func: fn(Lua, List(Value)) -> #(Lua, List(Value)))
66+
}
67+
68+
@external(erlang, "glua_ffi", "is_lua_func")
69+
fn is_lua_func(a: anything) -> Bool
70+
71+
@external(erlang, "glua_ffi", "coerce")
72+
fn coerce_lua_func(a: anything) -> LuaFunc
73+
74+
pub fn decode_func() {
75+
use then <- decode.then(decode.dynamic)
76+
echo then
77+
case is_lua_func(then) {
78+
True -> decode.success(coerce_lua_func(then))
79+
False ->
80+
decode.failure(
81+
LuaFunc(func: fn(_, _) { panic as "unreachable" }),
82+
"LuaFunc",
83+
)
84+
}
85+
}
86+
87+
// {NewState, Ret} = Fun(State, Transformed),
88+
6289
pub opaque type Output {
6390
Output(lua: Lua, refs: List(ValueRef))
6491
}
@@ -169,17 +196,14 @@ pub fn function(
169196
f: fn(Lua, List(dynamic.Dynamic)) -> #(Lua, List(Value)),
170197
) -> Value {
171198
// we need a little wrapper for functions to satisfy luerl's order of arguments and return value type
172-
wrap_trans_function(f)
199+
function_transform(f, default_transformation)
173200
}
174201

175202
pub fn function_transform(
176203
f: fn(Lua, List(dynamic.Dynamic)) -> #(Lua, List(Value)),
177204
transformation: Transformation,
178205
) -> Value {
179-
case transformation {
180-
NoTransform -> wrap_function(f)
181-
ProplistTransform -> wrap_trans_function(f)
182-
}
206+
wrap_function(f, transformation)
183207
// we need a little wrapper for functions to satisfy luerl's order of arguments and return value type
184208
}
185209

@@ -236,11 +260,7 @@ pub fn userdata(v: anything) -> Value
236260
@external(erlang, "glua_ffi", "wrap_fun")
237261
fn wrap_function(
238262
fun: fn(Lua, List(dynamic.Dynamic)) -> #(Lua, List(Value)),
239-
) -> Value
240-
241-
@external(erlang, "glua_ffi", "wrap_trans_fun")
242-
fn wrap_trans_function(
243-
fun: fn(Lua, List(dynamic.Dynamic)) -> #(Lua, List(Value)),
263+
transformation: Transformation,
244264
) -> Value
245265

246266
@external(erlang, "luerl", "decode_list")
@@ -249,21 +269,32 @@ fn do_decode_list(ref: List(ValueRef), lua: Lua) -> List(dynamic.Dynamic)
249269
@external(erlang, "luerl", "decode")
250270
fn do_decode(ref: ValueRef, lua: Lua) -> dynamic.Dynamic
251271

252-
@external(erlang, "glua_ffi", "proplist_to_map")
253-
fn proplist_to_map(a: anything) -> dynamic.Dynamic
272+
/// Precondition: ONLY luerl decoded terms can be added here or else it will SPECTACULARLY FAIL
273+
@external(erlang, "glua_ffi", "transform")
274+
fn transform(a: luerl_value, transformation: Transformation) -> dynamic.Dynamic
254275

276+
/// Internally in luerl, when a `Value` is decoded it has proplists and difficult to decode functions.
277+
/// This poses some problems; proplists make decoding difficult and erlang functions are hard to decode in gleam.
278+
///
279+
/// Glua has decided to greatly improve type safety at a marginal performance cost.
280+
/// If you wish to remove these transformations for whatever reason, you may use the functions that accepts a `Transformation`.
255281
pub type Transformation {
256-
NoTransform
257-
/// Default
258-
ProplistTransform
282+
Transformation(
283+
/// If true, transforms proplists (ex. `[#("key", "value")]`) into dicts
284+
proplist: Bool,
285+
/// If true, transforms functions to `LuaFunc`s making them callable with glua
286+
func: Bool,
287+
)
259288
}
260289

290+
pub const default_transformation = Transformation(proplist: True, func: True)
291+
261292
/// The decoder will always receive a list of values
262293
pub fn dec(
263294
output: Result(Output, LuaError),
264295
using decoder: decode.Decoder(a),
265296
) -> Result(#(Lua, a), LuaError) {
266-
dec_transform(output, decoder, ProplistTransform)
297+
dec_transform(output, decoder, default_transformation)
267298
}
268299

269300
pub fn dec_transform(
@@ -272,16 +303,9 @@ pub fn dec_transform(
272303
transformation tf: Transformation,
273304
) {
274305
use output <- result.try(output)
275-
let dyn =
276-
do_decode_list(output.refs, output.lua)
277-
|> dynamic.list()
278-
279-
let dyn = case tf {
280-
NoTransform -> dyn
281-
ProplistTransform -> proplist_to_map(dyn)
282-
}
283-
284-
dyn
306+
do_decode_list(output.refs, output.lua)
307+
|> list.map(transform(_, tf))
308+
|> dynamic.list()
285309
|> decode.run(decoder)
286310
|> result.map(pair.new(output.lua, _))
287311
|> result.map_error(UnexpectedResultType)
@@ -292,7 +316,7 @@ pub fn dec_one(
292316
output: Result(Output, LuaError),
293317
using decoder: decode.Decoder(a),
294318
) -> Result(#(Lua, a), LuaError) {
295-
dec_one_transform(output, decoder, ProplistTransform)
319+
dec_one_transform(output, decoder, default_transformation)
296320
}
297321

298322
/// Assume there will be only one item to decode
@@ -641,27 +665,43 @@ fn do_eval_file(
641665
path: String,
642666
) -> Result(#(Lua, List(ValueRef)), LuaError)
643667

644-
pub fn call_function(
668+
pub fn call_function_ref(
645669
state lua: Lua,
646670
ref fun: ValueRef,
647671
args args: List(Value),
648672
) -> Result(Output, LuaError) {
649-
do_call_function(lua, fun, args)
673+
do_call_function_ref(lua, fun, args)
650674
|> result.map(to_output)
651675
}
652676

653677
@external(erlang, "glua_ffi", "call_function")
654-
fn do_call_function(
678+
fn do_call_function_ref(
655679
lua: Lua,
656680
fun: ValueRef,
657681
args: List(Value),
658682
) -> Result(#(Lua, List(ValueRef)), LuaError)
659683

684+
@external(erlang, "glua_ffi", "call_enc_function")
685+
fn do_call_function(
686+
lua: Lua,
687+
fun: LuaFunc,
688+
args: List(Value),
689+
) -> Result(#(Lua, List(ValueRef)), LuaError)
690+
691+
pub fn call_function(
692+
state lua: Lua,
693+
func func: LuaFunc,
694+
args args: List(Value),
695+
) -> Result(Output, LuaError) {
696+
do_call_function(lua, func, args)
697+
|> result.map(to_output)
698+
}
699+
660700
pub fn call_function_by_name(
661701
state lua: Lua,
662702
keys keys: List(String),
663703
args args: List(Value),
664704
) -> Result(Output, LuaError) {
665705
use fun <- result.try(get(lua, keys))
666-
call_function(lua, fun.ref, args)
706+
call_function_ref(lua, fun.ref, args)
667707
}

src/glua_ffi.erl

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,23 @@
22

33
-import(luerl_lib, [lua_error/2]).
44

5-
-export([coerce/1, coerce_nil/0, coerce_userdata/1, wrap_fun/1, wrap_trans_fun/1, sandbox_fun/1,
5+
-export([coerce/1, coerce_nil/0, coerce_userdata/1, wrap_fun/2, sandbox_fun/1,
66
get_table_keys/2, get_private/2, set_table_keys/3, load/2, load_file/2, eval/2, eval_file/2,
7-
eval_chunk/2, call_function/3, alloc/2, proplist_to_map/1]).
8-
9-
% This could be improved on perhaps
10-
proplist_to_map(Term) when is_list(Term) ->
11-
case is_proplist(Term) of
12-
true ->
13-
maps:from_list([{K, proplist_to_map(V)} || {K, V} <- Term]);
14-
false ->
15-
case Term of
16-
[] -> #{};
17-
_ -> [proplist_to_map(E) || E <- Term]
18-
end
7+
eval_chunk/2, call_function/3, call_enc_function/3, is_lua_func/1, alloc/2, transform/2]).
8+
9+
% Precondition: [[See glua.gleam]]
10+
transform(Term, {transformation, false, false}) ->
11+
Term;
12+
transform(Term, Settings = {transformation, Proplist, _Funcs}) when is_list(Term) ->
13+
Result = [{K, transform(V, Settings)} || {K, V} <- Term],
14+
case Proplist of
15+
true -> maps:from_list(Result);
16+
false -> Result
1917
end;
20-
proplist_to_map(Term) when is_map(Term) ->
21-
maps:from_list([{K, proplist_to_map(V)} || {K, V} <- maps:to_list(Term)]);
22-
proplist_to_map(Term) when is_tuple(Term) ->
23-
list_to_tuple([proplist_to_map(E) || E <- tuple_to_list(Term)]);
24-
proplist_to_map(Other) ->
25-
Other.
26-
27-
is_proplist(L) when is_list(L), L =/= [] ->
28-
lists:all(fun(E) ->
29-
is_tuple(E) andalso tuple_size(E) =:= 2
30-
end, L);
31-
is_proplist(_) ->
32-
false.
18+
transform(Func, {transformation, _Proplist, true}) when is_function(Func, 2) ->
19+
{lua_func, Func};
20+
transform(Term, {transformation, _Proplist, _Funcs}) ->
21+
Term.
3322

3423
%% turn `{userdata, Data}` into `Data` to make it more easy to decode it in Gleam
3524
maybe_process_userdata(Lst) when is_list(Lst) ->
@@ -146,17 +135,10 @@ alloc(St0, {usrdef,_}=Value) ->
146135
alloc(St0, Other) ->
147136
{St0, Other}.
148137

149-
wrap_fun(Fun) ->
150-
fun(Args, State) ->
151-
Decoded = luerl:decode_list(Args, State),
152-
{NewState, Ret} = Fun(State, Decoded),
153-
luerl:encode_list(Ret, NewState)
154-
end.
155-
156-
wrap_trans_fun(Fun) ->
138+
wrap_fun(Fun, Settings) ->
157139
fun(Args, State) ->
158140
Decoded = luerl:decode_list(Args, State),
159-
Transformed = [proplist_to_map(Arg) || Arg <- Decoded],
141+
Transformed = [transform(Arg, Settings) || Arg <- Decoded],
160142
{NewState, Ret} = Fun(State, Transformed),
161143
luerl:encode_list(Ret, NewState)
162144
end.
@@ -200,10 +182,18 @@ eval_file(Lua, Path) ->
200182
to_gleam(luerl:dofile(
201183
unicode:characters_to_list(Path), Lua)).
202184

185+
is_lua_func({lua_func, Func}) when is_function(Func, 2) ->
186+
true;
187+
is_lua_func(_Other) ->
188+
false.
189+
203190
call_function(Lua, Fun, Args) ->
204191
{EncodedArgs, State} = encode_list(Args, Lua),
205192
to_gleam(luerl:call(Fun, EncodedArgs, State)).
206193

194+
call_enc_function(Lua, {lua_func, Func}, Args) ->
195+
Func(Args).
196+
207197
get_private(Lua, Key) ->
208198
try
209199
{ok, luerl:get_private(Key, Lua)}

test/ffi_test.gleam

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)